Skip to content

Commit ca8c238

Browse files
committed
Add preview guidance.
1 parent d473537 commit ca8c238

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed

docs/supporting-new-frameworks.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# Introduction
2+
When adopting new features from recent .NET releases in OData.net, we may encounter pre-release APIs that could be useful to integrate. These pre-release versions often interface with public APIs but may not yet offer backporting options for older .NET versions. However, leveraging them through preprocessor directives is a common practice in OData.net and other .NET projects.
3+
4+
5+
## Example : Adding new API present only in future .NET versions
6+
7+
```cs
8+
// Default API for lowest version that we compile for e.g. in main NET8.0
9+
IEdmEntitySet FindEntitySet(string setName);
10+
11+
// New API that targets a new version of .Net framework
12+
#if NET9_0_OR_GREATER
13+
IEdmEntitySet FindEntitySet(ReadOnlyMemory<char> setName);
14+
#endif
15+
```
16+
However we also will need to start providing support for newer dotnet versions with the new release plan and in order to support these we could also adopt a few new practices for consistency.
17+
18+
Over time, these APIs will be fully implemented using stable features in .NET 9. However, there are cases where exposing a pre-release API can provide immediate benefits, such as improving performance or enabling early adoption of upcoming .NET features.
19+
20+
## Motivation for Using Pre-Release APIs
21+
Pre-release APIs may:
22+
23+
* Improve performance: Offer optimizations such as reduced memory allocations.
24+
* Enhance future compatibility: Provide a migration path for upcoming .NET releases.
25+
* Enable experimentation: Allow users to try out new features before they become stable.
26+
27+
These APIs will eventually have full implementations that leverage stable .NET 9 features. However, there are cases where exposing a new API earlier can be beneficial—both for improving our projects and for preparing for upcoming .NET framework versions that are still finalizing preview features.
28+
29+
Preview APIs may:
30+
- Become fully supported in future stable releases.
31+
- Undergo modifications or redesigns before finalization.
32+
- Help maintain compatibility and code parity with existing stable APIs while offering immediate benefits.
33+
34+
### **Example: Optimizing URI Parsing for Zero Allocations**
35+
Consider a scenario where we want to introduce a new API to achieve **zero allocations** while parsing a URI.
36+
37+
#### **Current Implementation**
38+
```csharp
39+
public abstract class ODataPathSegment
40+
{
41+
/// <summary>Returns the identifier for this segment, i.e., the string part without the keys.</summary>
42+
public string Identifier { get; set; }
43+
// ... redacted
44+
}
45+
46+
// This references an interned string, so memory usage is optimized.
47+
this.Identifier = navigationProperty.Name;
48+
49+
// However, we perform lookups before creating PathSegments using FindProperty, FindEntitySet, and FindSingleton in ODataUriResolver.
50+
// Currently, resolving each segment requires a Find operation or string manipulation.
51+
52+
// We can achieve the same functionality more efficiently by using .NET 9 Find methods,
53+
// which perform zero allocations and avoid performance penalties.
54+
```
55+
56+
### **Adopting Preview APIs and Migration Strategy**
57+
To adopt these new APIs efficiently, we may need to:
58+
- Replace certain existing APIs and fields with non-allocating alternatives while ensuring compatibility with stable APIs.
59+
- Target upcoming APIs that are expected to be available by the final release date.
60+
- Introduce **gated preview APIs** that users can opt into, allowing early testing before official support in .NET 9 and .NET 10.
61+
62+
By carefully integrating these preview features, we can enhance performance while maintaining long-term support for stable .NET versions.
63+
64+
### Migration Strategy
65+
66+
## **Migration Strategy**
67+
### **Examples**
68+
69+
### **Case 1: Incremental Adoption (Self-Use)**
70+
In some scenarios, users may want to introduce changes gradually, allowing services or other projects to start using them before the official release.
71+
To facilitate this, **nightly or preview builds** can be gated using the [`[Experimental]` attribute](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/experimental-attribute) introduced in .NET 8.
72+
73+
#### **Proposal**
74+
- Introduce new features in a **non-breaking** way to maintain compatibility with existing APIs.
75+
- Ensure that any experimental API does not disrupt current functionality while allowing early adopters to test and provide feedback.
76+
77+
### **Case 2: Stable APIs and Breaking Changes**
78+
If an API is **public**, changing its signature is a breaking change. However, we may still want to enable users on newer frameworks to benefit from improvements while maintaining support for older implementations.
79+
80+
#### **Decision Points**
81+
- Should we provide an **alternative API** alongside the existing one?
82+
- Can we use **preprocessor directives** or conditional compilation to introduce changes only in newer framework versions?
83+
- Should we introduce an **opt-in mechanism**, such as preview attributes, to control adoption?
84+
85+
By carefully planning the migration strategy, we can balance innovation with stability, ensuring a smooth transition for users across different .NET versions.
86+
87+
### **Migration**
88+
#### **Case: Stable API's are used**
89+
** API is public and changing signature is breaking** but we also want to allow users using the old API in new frameworks to also benefit from the new API that we are adding which considerations should we make
90+
91+
Do we choose
92+
93+
**A**
94+
```cs
95+
public static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName)
96+
{
97+
EdmUtil.CheckArgumentNull(model, "model");
98+
EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName");
99+
100+
string fullyQualifiedName = model.ReplaceAlias(qualifiedName);
101+
#if NET9_0_OR_GREATER
102+
return FindAcrossModels(
103+
model,
104+
fullyQualifiedName.AsSpan(),
105+
findTerm,
106+
(first, second) => RegistrationHelper.CreateAmbiguousTermBinding(first, second));
107+
#else
108+
return FindAcrossModels( // call underlying method
109+
model,
110+
fullyQualifiedName,
111+
findTerm,
112+
(first, second) => RegistrationHelper.CreateAmbiguousTermBinding(first, second));
113+
#endif
114+
}
115+
116+
#if NET9_0_OR_GREATER
117+
// introduce new api
118+
public static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName)
119+
=> FindAcrossModels(
120+
model,
121+
fullyQualifiedName,
122+
findTerm,
123+
(first, second) => RegistrationHelper.CreateAmbiguousTermBinding(first, second));
124+
#endif
125+
```
126+
_OR_
127+
128+
**B**
129+
```cs
130+
public static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName)
131+
{
132+
EdmUtil.CheckArgumentNull(model, "model");
133+
EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName");
134+
135+
string fullyQualifiedName = model.ReplaceAlias(qualifiedName);
136+
#if NET9_0_OR_GREATER
137+
return FindTerm( /* call new API */
138+
model,
139+
fullyQualifiedName.AsSpan(),
140+
);
141+
#else
142+
return FindAcrossModels(
143+
model,
144+
fullyQualifiedName,
145+
findTerm,
146+
(first, second) => RegistrationHelper.CreateAmbiguousTermBinding(first, second));
147+
#endif
148+
}
149+
150+
#if NET9_0_OR_GREATER
151+
// introduce new api which is stable
152+
public static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName)
153+
=> FindAcrossModels(
154+
model,
155+
fullyQualifiedName,
156+
findTerm,
157+
(first, second) => RegistrationHelper.CreateAmbiguousTermBinding(first, second));
158+
#endif
159+
160+
```
161+
162+
### ** Private and internal APIS **
163+
For these we can afford to change the signatures or introduce a new parallel api during the transition period.
164+
165+
**A**
166+
167+
```cs
168+
#if NET9_0_OR_GREATER
169+
private static IEdmTerm FindTerm(this IEdmModel model, ReadOnlySpan<char> qualifiedName) // modifies the signature between the two
170+
#else
171+
private static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName)
172+
#endif
173+
{
174+
EdmUtil.CheckArgumentNull(model, "model");
175+
EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName");
176+
#if NET9_0_OR_GREATER
177+
ReadOnlySpan<char> fullyQualifiedName = model.ReplaceAlias(qualifiedName); // modifies body
178+
#else
179+
string fullyQualifiedName = model.ReplaceAlias(qualifiedName);
180+
#endif
181+
return FindAcrossModels(
182+
model,
183+
fullyQualifiedName,
184+
findTerm,
185+
(first, second) => RegistrationHelper.CreateAmbiguousTermBinding(first, second));
186+
187+
}
188+
```
189+
190+
**B**
191+
```cs
192+
#if NET9_0_OR_GREATER
193+
private static IEdmTerm FindTerm(this IEdmModel model, ReadOnlySpan<char> qualifiedName) { /*Body*/} // modifies the signature between the two
194+
#else
195+
private static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName) { /*Body*/}
196+
#endif
197+
```
198+
199+
### **Targeting an underlying api that is still in preview or releasing api in preview mode**
200+
When our codebase depends on a preview feature, we need to carefully manage its usage and communicate its status to users.
201+
Another case would be when users want to have a pre-release version with the API specification
202+
203+
**Scenario**
204+
We may encounter cases where:
205+
206+
* A preview API is available, and we want to use it in our project.
207+
* We want to introduce a new API that relies on a preview feature.
208+
209+
**Considerations**
210+
* Should we mark all APIs that depend on a preview feature with [Experimental](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/experimental-attribute), or only those that are uncertain and may not be supported in the future?
211+
* If a preview feature is critical to our implementation, should we provide an alternative fallback in case it is removed? e.g. In the case of Net9 AlternateLookup if _ReadOnlySpan_ was not supported we could use the string or _ReadOnlyMemory_ api's to keep the feature before releasing the library.
212+
* Should these preview features be in a partial class that can be removed or migrated to the main class when we promote the API to GA?
213+
e.g.
214+
215+
```cs
216+
public abstract partial class ODataPathSegment{} // stable api's inside ODataPathSegment.cs
217+
218+
public abstract partial class ODataPathSegment
219+
{
220+
// api that may not be fully ready before the next stable release or is using a preview sdk.
221+
} // inside ODataPathSegment.Future|Proposed|FeatureName.cs
222+
223+
```
224+
225+
**Note**: These will be for features that may be targetting a platform when the full release is not yet out. This could be quite exciting for AspNetCoreOData.

0 commit comments

Comments
 (0)