diff --git a/accepted/2020/net5/net5.md b/accepted/2020/net5/net5.md new file mode 100644 index 000000000..5278c8c66 --- /dev/null +++ b/accepted/2020/net5/net5.md @@ -0,0 +1,869 @@ + + + +# Target Framework Names in .NET 5 + +**PM** [Immo Landwerth](https://github.com/terrajobst) | +[Video Presentation](https://youtu.be/kKH8NzvtENQ?t=694) + +We'd like to drastically simplify the framework names (TFMs) developers must use +in project files and NuGet packages. This includes merging the concept of .NET 5 +and .NET Standard while still being able to use `#if` to use OS-specific APIs. +This document explains the motivation and resulting developer experience. + +.NET, as most technologies that are two decades old, has a lot of heritage, +especially in product naming and editions: .NET Framework, .NET Compact +Framework, Silverlight, .NET Micro Framework, .NET Portable Class Libraries, +.NET for Windows Store, .NET Native, .NET Core, .NET Standard... and that +doesn't even include what the Mono community built. While this evolution of .NET +can be explained (and was properly motivated) it created a massive tax: the +concept count. If you're new to .NET, where would you start? What is the latest +stack? You may say, “of course that's .NET Core” but how would anyone know that +by just looking at the names? + +We've simplified the world with .NET Standard, in that class library authors +don't have to think about all the different “boxes” that represent different +implementations of .NET. It did that by unifying the *API surface* of the +various .NET implementations. Ironically, this resulted in us having to add yet +another box, namely .NET Standard. + +To make the future saner, we must reduce the number of boxes. We don't want to +make .NET less flexible, but we want to reduce nonsensical differences that +purely resulted from us not being open source early enough. For example, +Mono/Xamarin/Unity are based on a different set of runtimes and frameworks than +the .NET Framework/Silverlight/UWP/.NET Core lineage. With .NET Standard, we +have started to remove the differences in the API surface. With .NET 5, the goal +is to converge these lineages onto a single product stack, thus unifying +their *implementations*. + +![](pic01.png) + +While we strive to provide an experience where you don't have to reason about +the different kinds of .NET, we still don't want to fully abstract away the +underlying OS, so you'll continue to be able to call OS specific APIs, be that +via P/Invokes, WinRT, or the Xamarin bindings for iOS and Android. + +Now think about developers who start on this stack and can write any application +for any of the platforms that .NET provides support for. The branding we +currently have makes no sense to them. To find documentation and tutorials, the +only two things a developer should need to know is the name and version of their +technology stack. + +Let's contrast this with some of the NuGet packages that developers have to +author for today's world: + +![](pic02.png) + +There are a lot of names and version numbers. Knowing who is compatible with who +is impossible without a decoder ring. We've simplified this greatly with .NET +Standard, but this still requires a table that maps .NET Standard versions to +.NET implementation versions. + +The proposal is to reuse the existing `net` TFM and model OS-specific APIs on +top via a new syntax: + +* `net5.0`. This TFM is for code that runs everywhere. It combines and replaces + the `netcoreapp` and `netstandard` names. This TFM will generally only include + technologies that work cross-platform (modulo pragmatic concessions, like we + already did in .NET Standard). + +* `net5.0-android`, `net5.0-ios`, and `net5.0-windows`. These TFMs represent OS + specific flavors of .NET 5 that include `net5.0` plus OS-specific bindings. + +NuGet should use this new syntax to automatically understand that `net5.0` can +be consumed from `net6.0-windows` (but not the other way around). More +importantly, this notation will also enable developers to intuitively understand +compatibility relationships because they are expressed by naming, rather than by +mapping tables. Yay! + +## Scenarios and User Experience + +### Vary implementation + +Ida is working on a Xamarin Forms application that supports Android, iOS, and +Windows. Her application needs GPS information, but only a very limited set. +Since there is no portable GPS API, she writes her own little abstraction +library using multi-targeting. + +By doing so, she's able to encapsulate the GPS access without having to +multi-target her entire application, just this one area. + +```C# +public static class GpsLocation +{ + public static bool IsSupported + { + get + { +#if ANDROID || IOS || WINDOWS + return true; +#else + return false; +#endif + } + } + + public static (double Latitude, double Longitude) GetCoordinates() + { +#if ANDROID + return AndroidAPI(); +#elif IOS + return AppleAPI(); +#elif WINDOWS + return WindowsAPI(); +#else + throw new PlatformNotSupportedException(); +#endif + } +} +``` + +### Vary API surface + +Ada is a developer on SkiaSharp, a cross-platform 2D graphics API for .NET +platforms based on Google's Skia Graphics Library. The project is already using +multi-targeting to provide different implementations for different platforms. To +make it easier to use, she's adding a new `SkiaSharpImage` type, which +represents a bitmap and is constructed via OS-provided data types. Ada uses +`#if` to expose different constructors on different platforms: + +```C# +public static class SkiaSharpImage +{ +#if ANDROID + public SkiaSharpImage(Android.Media.Image nativeImage) { /* ... */ } +#endif + +#if IOS + public SkiaSharpImage(NSImage nativeImage) { /* ... */ } +#endif + +#if WINDOWS + public SkiaSharpImage(Windows.Media.BitmapImage nativeImage) { /* ... */ } +#endif +} +``` + +### Upgrading the OS bindings + +Miguel is building Baby Shark, a popular iOS application. He started with .NET +that supported iOS 13 but Apple just released iOS 14. He downloads the updated +version of the .NET 5 SDK which now also includes support for iOS 14. In order +to gain access to the new APIs that Apple has added, Miguel opens his project +file which currently looks like this: + +```XML + + + + net5.0-ios13.0 + + + ... + + +``` + +He modifies the `` to be `net5.0-ios14.0`. + +### Lighting up on later OS versions + +Miguel doesn't want to cut off his users who are currently on iOS 13, so he +wants to continue to have his application work on iOS 13 as well. To achieve +that, Miguel modifies the project file by adding ``: + +```XML + + + + net5.0-ios14.0 + 13.0 + + + ... + + +``` + +However, since Miguel also uses the new `NSFizBuzz` API that Apple added in iOS +14, he also modifies his source code to check for the operating system version +before calling it: + +```C# +public void OnClick(object sender, EventArgs e) +{ + if (Environment.OSVersion.Version >= new Version(14, 0)) + { + NSFizBuzz(); + } +} +``` + +### Consuming a library with a higher `TargetPlatformMinVersion` + +After using `NSFizzBuzz` directly for a while, Miguel notices that these OS APIs +are a bit hard to use, so he looks for a .NET library. He finds +`Monkey.FizzBuzz`, which he tries to reference, which succeeds. However, when +building his application he gets the following warning: + +> warning NU1702: Package 'Monkey.FizzBuzz' was restored using 'net5.0-ios14' +> and has 'TargetPlatformMinVersion' of '14.0' while the project has a value of +> '13.0'. You should either upgrade your project to '14.0' or only make calls +> into the library after checking that the OS version is '14.0' or higher. + +Since Miguel already guarded all method calls, he simply suppresses the warning. + +### Consuming a library with a higher `TargetPlatformVersion` + +After the success of using `Monkey.FizzBuzz` in his Baby Shark app, Miguel wants +to use it everywhere now, so he decides to use it in his existing Laserizer 5000 +app. However, when he adds a reference to `Monkey.FizzBuzz`, he gets an error +from NuGet: + +> error NU1202: Package 'Monkey.FizzBuzz' is not compatible with +> 'net5.0-ios13.0'. Package 'Monkey.FizzBuzz' supports: net5.0-ios14.0 + +So Miguel edits his project file by changing `net5.0-ios13.0` to +`net5.0-ios14.0` which fixes the error. + +### Using a higher `TargetPlatformVersion` than the current SDK + +Claire has the first release of the .NET 5 SDK that only ships bindings +for iOS 13. She clones Miguel's Baby Shark repo from GitHub and tries to +build it on her machine. Since Baby Shark targets `net5.0-ios14.0` she +gets a build error: + +> error NETSDK1045: The current .NET SDK does not support targeting iOS 14.0. +> Either target iOS 13.0, or use a version of the .NET SDK that supports iOS +> 14.0. [BabyShark.csproj] + +## Requirements + +### Goals + +* Use naming that aligns with product strategy +* Merge .NET Core and .NET Standard into a single concept +* Developers should be able to understand compatibility relationships without + having to consult a mapping table. +* Provide compatibility with existing concepts and NuGet packages +* It would be great to get this into early previews of .NET 5 +* Support multi-targeting different versions of the same OS +* Don't force multi-targeting for different versions of the same OS. It should + be possible to produce a single binary that can use newer APIs when calls are + properly guarded with an OS check. + +### Non-Goals + +* Replace TFMs or expand runtime identifiers (RIDs) + +## Design + +We'll have the following TFMs: + +| TFM | Compatible With | Comments | +|-----------------|------------------------------------------------------------|-----------------------------------| +| net5.0 | net1..4 (with NU1701 warning) | No WinForms or WPF | +| | netcoreapp1..3.1 (warning when WinForms/WPF is referenced) | | +| | netstandard1..2.1 | | +| net5.0-android | xamarin.android | | +| | (+everything else inherited from net5.0) | | +| net5.0-ios | xamarin.ios | | +| | (+everything else inherited from net5.0) | | +| net5.0-macos | xamarin.mac | | +| | (+everything else inherited from net5.0) | | +| net5.0-tvos | xamarin.tvos | | +| | (+everything else inherited from net5.0) | | +| net5.0-watchos | xamarin.watchos | | +| | (+everything else inherited from net5.0) | | +| net5.0-windows | netcoreapp1..3.1 | WinForms + WPF | +| | (+everything else inherited from net5.0) | | +| Tizen, Unity... | Will follow the Xamarin model | | + +### OS versions + +The TFM will support an optional OS version, such as: + +* `net5.0-ios` +* `net5.0-ios12.0` + +A TFM without an OS version will be interpreted as the lowest OS version that +was supported by the corresponding `netX.Y` version. + +> *Note: One assumption here is that developers can always compile for the +> latest OS API set and run on older OS versions so long they guard method calls +> correctly. This is how P/Invokes, WinRT, iOS, and Android bindings work +> today.* + +For example, say that when .NET 5 ships, the version of iOS that we include +bindings for is iOS 13. That would mean that `net5.0-ios` and `net5.0-ios13.0` +mean the exact same thing. Now lets say that Apple ships a new iOS 14 before +.NET 6 is ready. We'd release a new version of the .NET 5 SDK that adds support +for iOS 14. On machines with that SDK `net5.0-ios` still means `net5.0-ios13.0`. + +Please note that this mapping is specific for `net5.0`. When .NET 6 ships, we +could decide that `net6.0-ios` means `net6.0-ios14.0`. But even when you use the +.NET 6 SDK, when targeting .NET 5, `net5.0-ios` also still means +`net5.0-ios13.0`. So these mappings are immutable. + +We have done this to simplify the experience for application models where the OS +version is largely irrelevant, for example WinForms and WPF. Whether or not the +template will put an OS version in the `` is up to each +application model. Based on conversations, it seems we'll be landing on this: + +| TFM | Project file includes OS version +|-----------------|--------------------------------- +| net5.0-android | Yes +| net5.0-ios | Yes +| net5.0-macos | Yes +| net5.0-tvos | Yes +| net5.0-watchos | Yes +| net5.0-windows | No + +Please note that by being able to put the OS version into the TFM one can also +multi-target between OS versions: + +```XML + + net5.0-ios13.0;net5.0-ios14.0 + +``` + +However, it's a bit misleading to think of the OS version number as the version +of the operating system you're running on. Rather, it's the operating system's +API you're compiling for. + +People should really think of the TFM's OS version as the version number of the +.NET OS bindings. Under normal circumstances we'll rev that version in lock step +with the OS. This will make it easy to understand which version you need to gain +access to the APIs. However, there are cases where that's not possible. For +example, iOS API bindings aren't fully generated; they require manual work. +Depending on how large the API surface in a given OS update is we might decide +to finish the bindings for the most useful OS APIs first and add other bindings +later (which we have done in the past). If the OS normally ships APIs in +*major.minor* then we can use the third digit to indicate binding-only updates. +However, we don't control the OS version so in principle there is no guarantee +that they don't ship new APIs in third- or fourth digit updates. + +Let's look at a concrete example. Assume that we shipped .NET 5 with iOS 13.0 +support and Apple released an update to iOS with new APIs, say iOS 13.1. Let's +say it takes us a few updates to finish binding them all: + +TFM | Description +-------------------|---------------------------- +`net5.0-ios13.0` | The entirety of iOS 13.0 +`net5.0-ios13.1.1` | First batch of bindings for iOS 13.1. +`net5.0-ios13.1.2` | Second batch of bindings for iOS 13.1. +`net5.0-ios13.1.3` | Third batch of bindings for iOS 13.1. + +Now let's say that Apple decides to ship an iOS 13.1.1 with new APIs as well. As +you can see, we already used that number to indicate the first batch of 13.1 +APIs. No biggie, we'd just bind these APIs in the next available train, which +happens to be `net5.0-ios13.1.4`. + +We don't expect this to be too confusing because we generally encourage people +to compile against the latest available OS API set and use +`TargetPlatformMinVersion` in order to run on older versions. The reference +assembly for the OS bindings will also include an attribute per API indicating +which OS version is required. These attributes, as well as +`TargetPlatformMinVersion`, will always be set to actual OS versions. + +So in practice we don't expect people having to match TFMs to specific OS +version but it's good to have numbers that are "close" enough to give project +maintainers some idea of what's available to them. + +### Mapping to properties + +These are the relevant MSBuild properties: + +| Property | Meaning | Examples | +|---------------------------------------|---------------------------------|----------------------------------| +| `TargetFramework` (TFM) | The friendly name | `net4`, `netcoreapp3.0` | +| `TargetFrameworkIdentifier` (TFI) | The long name | `.NETFramework` or `.NETCoreApp` | +| `TargetFrameworkVersion` (TFV) | The version number | `2`, `3.0`, `3.1` | +| `TargetFrameworkProfile` (TFP) | The profile | `Client` or `Profile124` | +| `TargetPlatformIdentifier` (TPI) | The OS platform | `iOS`, `Android`, `Windows` | +| `TargetPlatformVersion` (TPV) | The OS platform version | `12.0` or `13.0` | +| `TargetPlatformMinVersion` (TPMV) | The minimum OS platform version | `12.0` or `13.0` | + +We're going to map the TFMs as follows: + +| TF | TFI | TFV | TFP | TPI | TPV | TPMV +|--------------------|---------------|---------|-----|---------|-----|---------------- +| net4.X | .NETFramework | 4.X | | | | +| net5.0 | .NETCoreApp | 5.0 | | | | +| net5.0-androidX.Y | .NETCoreApp | 5.0 | | Android | X.Y | X.Y (defaulted) +| net5.0-iosX.Y | .NETCoreApp | 5.0 | | iOS | X.Y | X.Y (defaulted) +| net5.0-windowsX.Y | .NETCoreApp | 5.0 | | Windows | X.Y | X.Y (defaulted) + +Specifically: + +* **We'll continue to use .NETCoreApp as the TFI**. This reduces the number of + places build logic needs to change in MSBuild files (both built-in targets as + well as third party targets deployed via NuGet packages). + +* **net4x and earlier will continue to use .NETFramework as the TFI**. This + means that `net4x` and `net5.0` aren't considered compatible by default, but + the compatibility will continue to be provided by .NET Framework compatibility + mode we introduced in .NET Standard 2.0. It's handled via + `AssetTargetFallback` in NuGet restore which also means consumers from + `net5.0` will continue to get a proper warning. + +* **TargetPlatformMinVersion is defaulted to TargetPlatformVersion**. However, + the customer can override this in the project file to a lower version (using a + higher version than `TargetPlatformVersion` should generate an error). + +_**Open Issue**. Please note that `net5.0`+ will map the TFI to `.NETCoreApp`. +We need to announce this change so that package authors with custom .props and +.targets are prepared. Link to DavKean's doc on how to do it._ + +_**Open Issue**. We should try to keep the TFI out of the .nuspec file. It seems +NuGet uses the long form `.NETFramework,Version=4.5` in the dependency groups. +We may want to change NuGet to allow the friendly name there as well and update +our packaging tools to re-write to friendly name on pack._ + +### Window-specific behavior + +_**Open Issue**. Review with WinForms/WPF & Windows folks._ + +* We need to define what the Windows version number is. It should probably be + the minimum for WPF/WinForms (because that makes the most sense until we ship + support for WinRT in .NET 5). Generally speaking, we expect UWP flavors to + burn in into the project file, just like iOS and Android. + +* When would the WinRT APIs be referenced? Should they show up by default + (assuming the project specified they correct version for the WinRT bindings) + or should there be an equivalent for `UseWindowsForms`? We probably don't want + an opt-in for the foundational WinRT APIs but maybe for the UI layer. + +### NuGet pack behavior + +We need to update the .nuspec format to allow embedding target platform +information per TFM. + +For that, I propose to add a `platforms` element under `metadata`. + +> **Note** This is just meant to be a strawman for NuGet so we can talk about +> semantics. The NuGet team should specify the actual encoding. Once spec is +> available, we'll link it from here. + +For each `netX.Y-{os}{version}`, it should contain a `platform` that ties the +TFM as specified to their corresponding `TargetPlatformVersion` and +`TargetPlatformMinVersion` entries: + +```xml + + + ClassLibrary3 + 1.0.0 + ClassLibrary3 + ClassLibrary3 + false + Package Description + + + + + + + +``` + +We want to make sure that we preserve the TFM from the project file: + +* **TFM doesn't contain an OS**. If the TFM is just the neutral `netX.Y` TFM + then the `.nuspec`'s `` element shouldn't list the TFM. If there are + no `` elements, the `` element should be omitted as well. + +* **TFM has an OS but no OS version**. If the user omits the OS version number + from the project's `` property, all usages of the TFM should + not contain an OS version number, including the project's output directory, + the `lib` folder in the NuGet package, and other corresponding entries in the + .nuspec. However, the effective OS version will be recorded in the `.nuspec`'s + `` element that corresponds to the TFM. + +* **TFM has both OS and OS version**. If the project did contain an OS version + for `` this should also be reflected by the project's output + directory, `lib` folder, and TFM references in the .nuspec. + +This means that in some cases the `platformVersion` attribute will have +redundant information but it will also always be the source of truth. It allows +NuGet to know the OS version even if the OS version isn't included in the TFM. + +For cases where someones wants to pack two `lib` folders like this: + +* `net5.0-ios` +* `net5.0-ios13.0` + +and `net5.0-ios`'s is mapped to be platform version `13.0`, the `pack` operation +should fail because both `lib` folders are representing the same target (the +same applies to other usages of the TFM in the `ref` and `runtime` folders). + +### NuGet installation behavior + +The TFM part before the dash will behave as today: projects can reference the +same or an older version of `netX.Y`, but not a newer version. Trying to do +should result in a package installation failure. The OS portion will follow the +same rules. + +The rationale is the same for both: when the library author compiled for a +higher version, it had access to more APIs, which the consuming project doesn't +have. This can cause compilation errors due to unresolved types as well as +runtime errors due to unresolved members. + +The world is a bit different for `TargetPlatformMinVersion`. NuGet should allow +installation of packages whose `TargetPlatformMinVersion` is higher than the +consuming project. The rationale here is that the consuming project might only +want to use the library on higher versions of the operating system and can +conditionally call the library code. The behavior should be similar to +`AssetTargetFallback` where installation of the package succeeds but each time +the project is built a warning is produced, which must be suppressible by using +the `` property in the project file or on the `` item. + +### TFMs are a closed set + +One question is whether third parties can extend the TFM space (that is, the +part after the dash) without having to rely on changes in NuGet/MSBuild. Due to +the expansion of friendly name into TFI & TFM this would be non-trivial. + +We may open this up in the future, but for now the consensus was that we'd +prefer to have the specific list of names that are expanded by NuGet. + +### What about .NET 10? + +Due to the fact that we're planning to bump the major version every year, we +have to think about what will happen with version parsing in case of two digit +version numbers, such as `net10`. Since `net10` already has a meaning (.NET +Framework 1.0), we need to keep it that way. To avoid surprises, we'll by default +use dotted version numbers in project templates to push developers towards being +explicit. + +| Framework | Identifier | Version| Comment +|----------------|---------------|--------|---------------------------------- +| net5 | .NETCoreApp | 5.0 | Will work, but shouldn't be used. +| net5.0 | .NETCoreApp | 5.0 | +| net10 | .NETFramework | 1.0 | +| net10.0 | .NETCoreApp | 10.0 | + +### Preprocessor Symbols + +Today, SDK-style projects automatically define `#if` symbols based on the +friendly TFM representation. This includes both versionless- as well as +version-specific symbols: + +TFM | Project-style | Automatic Defines +----------------|---------------|------------------------------------- +`net45` | CSPROJ | +`net45` | SDK-style | `NETFRAMEWORK`, `NET45` +`net48` | CSPROJ | +`net48` | SDK-style | `NETFRAMEWORK`, `NET48` +`netcoreapp3.1` | SDK-style | `NETCOREAPP`, `NETCOREAPP3_1` + +For .NET 5 and higher we plan to define the following symbols: + +TFM | Automatic Defines +--------------------|------------------------------------------------------- +`netX.Y` | `NETCOREAPP`, `NET`, `NETX_Y` +`netX.Y-iosA.B` | `NETCOREAPP`, `NET`, `NETX_Y`, `IOS`, `IOSA_B` +`netX.Y-androidA.B` | `NETCOREAPP`, `NET`, `NETX_Y`, `ANDROID`, `ANDROIDA_B` +`netX.Y-windowsA.B` | `NETCOREAPP`, `NET`, `NETX_Y`, `WINDOWS`, `WINDOWSA_B` + +Specifically: + +* We continue to define `NETCOREAPP` for backwards compatibility with existing + `#if` code. +* Moving forward we'll use `NET` as the versionless symbol (for .NET Framework + we used `NETFRAMEWORK`, so no conflict there). +* We'll follow the rule-based creation in SDK-style projects which takes the + friendly TFM name without the OS flavor, makes it upper-case and replaces + special characters with an underscore. +* For OS flavors we'll do the same, i.e. create versionless as well as version + specific symbols. + +In order to make it easier to update code, especially when doing +multi-targeting, we should make them additive, so that when targeting `net6.0` +both `NET6_0` and `NET5_0` are defined. The same applies to OS bindings. +Examples: + +* `net5.0` + * `NETCOREAPP`, `NETCOREAPP3_1` (for backwards compatibility) + * `NET`, `NET5_0` +* `net6.0` + * (same as `net5.0`) + * `NET6_0` +* `net5.0-ios13.0` + * (same as `net5.0`) + * `IOS`, `IOS13_0` +* `net5.0-ios14.0` + * (same as `net5.0-ios13`) + * `IOS14_0` + +### What would we target? + +Everything that is universal or portable to many platforms will target `net5.0`. +This includes most libraries but also ASP.NET Core and EF. + +Platform-specific libraries would target platform-specific flavors. For example, +WinForms and WPF controls would target `net5.0-windows`. + +Cross-platform application models (Xamarin Forms, ASP.NET Core) and bridge packs +(Xamarin Essentials) would at least target `net5.0` but might also additionally +target platform-specific flavors to light-up more APIs or features. This +approach is also called bait & switch. + +### TFMs in the UI + +There are some places in the IDE where targeting information is displayed: + +![](pic03.png) + +![](pic04.png) + +![](pic05.png) + +![](pic06.png) + +| Rule | Affected UI | +|-------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| For most UI, we should use the TFM friendly name (for example, `netcoreapp3.1`, `net5.0`, or `net5.0-ios`). | Solution Explorer, Editor Context Switcher, Debug Context Switcher | +| For cases where we use branded display names, we should use the name .NET 5. | Project Properties | + +### Related work + +To support multi-targeting end-to-end the TFM unification isn't enough. We also +need to unify the SDKs between .NET Core, Windows desktop, Xamarin, and +maybe ASP.NET Core. Without it, multi-targeting doesn't work without having to use +3rd party tools, such as Claire's +[MSBuild.Extras](https://github.com/novotnyllc/MSBuildSdkExtras). + +We should also make it easier to catch cases where some APIs aren't universally +available anymore. This includes fundamentals like threading for platforms like +WASM but also version-to-version differences in OS API surface. We already have +[an analyzer](https://github.com/dotnet/platform-compat), but we need to expand +this and make it a key capability that is available out-of-the-box. + +* [Making it easier to do TFM checks in MSBuild](https://github.com/microsoft/msbuild/issues/5171) + +## Q & A + +### Why can't we just have a single TFM for .NET 5? + +Moving forward, we can assume that the base class library (BCL) of .NET is the +same across all environments of .NET. One can think of `net5.0` as .NET Standard +but with an implementation (.NET Core). + +However, `net5.0` will not include .NET projections of OS APIs, such as: + +* WinRT +* iOS bindings +* Android bindings +* Web assembly host APIs + +We *could* make all APIs available everywhere. For example, we could create one +NuGet package per OS-platform and include two implementations, one that throws +and one that works. The package would use RIDs to select the correct +implementation, but it would have a uniform API surface. Callers of those +packages would use platform checks or catch `PlatformNotSupportedException`. + +We believe this isn't the right approach for two reasons: + +1. **Number of moving pieces**. Imagine what a simple class library would look + like that just wants to provide an abstraction over a single concept, such as + the GPS. It would transitively depend on all OS bindings. Regardless of the + platform you're building your application for, the output folder would have + to include all bindings across OS platforms, with all being throwing + implementations except for the one that your app is targeting. While we could + build tooling (such as a linker) that could remove this and replace the call + sites with throw statements, it seems backwards to first create this mess and + then rely on tooling to clean it up. It would be better to avoid this problem + by construction. + +2. **Versioning issues**. By making the OS bindings available on top of the .NET + platform the consumer is now able to upgrade the .NET platform and the OS + bindings independently, which makes it hard to explain what combinations are + supported. In practice, the OS bindings will want to depend on .NET types + which might change over time (think `Task` or `Span)`, so not every + combination can work. + +We believe it's much easier if we enable code to use multi-targeting (that is, +compile the same code for multiple platforms, like we do today. + +### Is .NET 5 a superset of .NET Framework 4.x? + +No, .NET 5 isn't a *superset* of .NET Framework. However, .NET 5 is +the *successor* of both .NET Core 3.x as well as .NET Framework 4.x. Starting +with .NET Core 3, you can build the same kind of workloads that you can with +.NET Framework. Some of the tech has changed, but that's the way it's [going to +be forever](https://devblogs.microsoft.com/dotnet/net-core-is-the-future-of-net/). +The remaining delta will never be closed. Existing apps can stay on .NET +Framework and will be supported, but we already said we'll no longer add new +features to it. + +Thus, new apps should start on .NET Core. By branding it as .NET 5, this +recommendation is much more obvious to both existing customers and new +customers. + +### ~~Why are the OS specific flavors not versioned by the OS?~~ + +*No longer applicable as we decided to allow that.* + +There are a couple of reasons why this isn't desirable: + +1. **It results a combinatorial explosion**. A TFM in the form of + `net5.0-windows7` would (syntactically) make any combination of .NET and OS + possible. This raises the question which combinations are supported, which + puts us back into having to provide the customer with a decoder ring. + +2. **It can make asset selection ill-defined**. Suppose the project is targeting + `net7.0-windows10`. The package offers `net5.0-windows10.0` and + `net6.0-windows7.0`. Now neither asset would be better. + +3. **A single version isn't enough**. Logically, you need at least two version + numbers to support OS targeting: + + * *Minimum OS API Level*. Consumers of the library must use an SDK that + support this version, or a higher version. This allows library authors + to use types from the OS bindings in their public API surface without + causing type resolution issues for the consumer. + + * *Minimum OS version*. For OS calls, it's common to guard OS calls with + version checks. This allows library authors to light up for later OS + versions without having to produce multiple binaries. In order for that + to be sound, a project generally cannot have lower version than whatever + its libraries support, unless the consumer guards their calls into that + library. + + Some platforms, such as Android, also have the notion of a *targeted OS + version* that indicates to the OS what behavior the author tested for. When + running on a later OS version, the OS might "quirk" the behavior to preserve + backwards compatibility. + +Developers will want to target different OS versions from a single code +base/NuGet package, but that doesn't mean they will need to use multi-targeting. +Multi-targeting is a very heavy hammer. Yes, many people are using it to target +different versions of .NET (for example, `net45` vs `net461`). But that's not +necessarily because `#if` is the better experience, it's because it's simply not +possible any other way, due to .NET runtime constraints (that is, assembly +references and type/member references need to be resolved by the JIT). This +problem doesn't exist for OS APIs. Developers can generally build a single +binary for multiple versions of an operating system. The calls to APIs +introduced in later versions must be guarded, but this is generally understood, +and we have some limited tooling already with plans to extend it. + +### How can I ensure my NuGet package/project can express OS requirements? + +We do want to allow the developer to express a minimum version they require for +the OS. It will likely be expressed as additional properties in the project file +that are also being persisted in the resulting NuGet package as well. It's not +decided on whether violating the perquisites will always be an error. For +example, we could decide to make *Minimum OS API Level* an error (because it +might result in compilation errors) while making *Minimum OS version* a warning +(because the consuming project could guard the method calls into the library). + +There is a [separate doc][os-versioning] that we're working on. + +### Why is there no TFM for Blazor? + +Based on conversations with the Blazor team we decided to not create a TFM for +WASM in the browser. That's because Blazor wants agility where code can +transparently work on client and server. Thus, they will continue to use +`net5.0`. The browser-specific APIs will be delivered as a NuGet package, using +RIDs to provide a throwing and non-throwing implementation. Other parties will +have to do the same thing. + +### ~~Why is the TFM called net5.0-browser and net5.0-wasm?~~ + +*No longer applicable as we won't have a TFM for Blazor.* + +WASM isn't a platform (in the OS sense) as much as it is an instruction set +architecture, so it's better to think of WASM as something like x86/x64. So, it +might not make sense to define `net5.0-wasm`. Rather, it would make more sense to +define `net5.0-browser`. The rationale is that the browser will run WASM in a +sandboxed environment which equates to having different native API sets. + +Any host that controls the JS runtime (for example, Node.js) could decide to expose +different/less constrained OS, which might give rise to other TFMs, such as +`net5.0-node`. + +### What about native dependencies? + +We don't plan to support varying API surface based on runtime characteristics +(x86/x64, AOT/JIT etc). This will continue to be supported via +the `runtime/` folder. + +### Will the new TFMs simplify the project files too? + +[@cartermp](https://github.com/cartermp) asked: + +> Does specifying `net5.0-windows` obviate the current three things you need to +> specify? +> +> * netcoreapp3.x +> * Desktop SDK attribute +> * UseWindowsForms/UseWPF +> +> IOW are we going to have to support both formats moving forward? What about +> converting apps? + +Generally, it does not. The idea I've heard is that all project types will be +unified to use `Sdk="Microsoft.NET.Sdk"` in order to make multi-targeting +easier. Customizations (for example, specific targets and references) would be brought +in via `UseXxx` properties, akin to how Windows Forms and WPF work in .NET Core +today. The reason is that in many cases the TFM alone isn't specific enough to +decide what kind of app are you building: + +* `net5.0`. Is a class library/console app, an ASP.NET Core app, or a Blazor app? +* `net5.0-windows`. Are you building a Windows Forms app or a WPF app? Are you + using both Windows Forms and WPF or just one? + +The nice thing about properties is that they naturally compose. If certain +combinations aren't possible, they can be blocked relatively easily. + +However, at this point it's still unclear whether the SDK unification will work +this way. One concern was that SDKs also bring in new item groups and might have +conflicting defaults for properties; this works today because the SDK can bring +in .props before the project file. When we rely on properties in the project +file, we need to bring those in the .targets (that is, the bottom of the project +file). While not impossible, this might force us to have knowledge in the base +SDK that can't be easily extended via optional components. +[@mhutch](https://github.com/mhutch) is working on a document specifically +around SDK convergence. + +### Why is there no TFM for Linux? + +The primary reason for OS specific TFMs is to vary API surface, not for varying +behavior. RIDs allow varying behavior and have support for various Linux +flavors. Specifically, TFMs aren't (primarily) meant to allow calling P/Invokes +under `#if`, most of the time that should be done by doing runtime checks or by +using RIDs. The primary reason for a TFM is to exclude large amounts of managed +representations for OS technologies (WinForms, WPF, Apple's NS APIs, Android +etc). + +Also, Android, iOS, macOS, and Windows share that they offer a stable ABI so +that exchanging binaries makes sense. Linux is too generic of a concept for +that, it's basically just the kernel, which again boils down to the only thing +you can do is calling P/Invokes. + +### Why is .NET 5.0's TFI still mapped to `.NETCoreApp`? + +In MSBuild you can't easily do comparisons like: + +```xml + +``` + +because that would be a string comparison. Rather, you need to do comparisons +like this: + +```xml + +``` + +By us mapping `net5.0` we break less of that code because existing code will +treat it correctly (i.e. as a future version of .NET Core) and also avoid +misclassification as .NET Framework. + +[os-versioning]: https://microsoft.sharepoint.com/:w:/t/DotNetTeam/EavsPfFy7lFLh7eQrdMN8QwBl05cGLPwrSzJeT8vEu32mw?e=knNQ6W diff --git a/accepted/2020/net5/pic01.png b/accepted/2020/net5/pic01.png new file mode 100644 index 000000000..d4fcf80fc Binary files /dev/null and b/accepted/2020/net5/pic01.png differ diff --git a/accepted/2020/net5/pic02.png b/accepted/2020/net5/pic02.png new file mode 100644 index 000000000..ae8d45254 Binary files /dev/null and b/accepted/2020/net5/pic02.png differ diff --git a/accepted/2020/net5/pic03.png b/accepted/2020/net5/pic03.png new file mode 100644 index 000000000..bed643a0e Binary files /dev/null and b/accepted/2020/net5/pic03.png differ diff --git a/accepted/2020/net5/pic04.png b/accepted/2020/net5/pic04.png new file mode 100644 index 000000000..86e2a4579 Binary files /dev/null and b/accepted/2020/net5/pic04.png differ diff --git a/accepted/2020/net5/pic05.png b/accepted/2020/net5/pic05.png new file mode 100644 index 000000000..0886ab665 Binary files /dev/null and b/accepted/2020/net5/pic05.png differ diff --git a/accepted/2020/net5/pic06.png b/accepted/2020/net5/pic06.png new file mode 100644 index 000000000..861a84589 Binary files /dev/null and b/accepted/2020/net5/pic06.png differ