An explanation is given of .NET Standard, when alternatives are useful and what alternatives are possible. Two examples of such alternatives are given.
Introduction
Many developers are familiar with .NET Standard . As the name suggests, it is a standard for .NET since .NET Framework is not the only supported platform. Creating a .NET Standard project, enables us to write and compile code to be used for multiple platforms. Which platforms are supported depends a bit on which .NET Standard version you choose but the general list is long: .NET Core, .NET Framework, Mono, Xamarin.iOS, Xamarin.Mac, Xamarin.Android, Universal Windows Platform and Unity.
That is good if your code is really generic, for example if you create a client package or implement a math model. However, once some code or NuGet Package has a dependency on some specific platform, .NET Standard is not going to be helpfull. .NET Standard is standard, not platform specific. So how to target multiple platforms in such a situation?
Background
It will be really helpfull if you have experience with .NET development and preferably with multiple platforms or multiple versions of a platform.
Using the code
The first step is really simple: target the TargetFrameworks
instead of the TargetFramework
in our project file. Since that is what we want to do anyway.
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
Now, we start coding a dummy class and see if it works.
public class DummyClass : IAsyncDisposable
{
public void DoSomething()
{
Console.WriteLine("Something needs is done");
}
public void Dispose()
{
Console.WriteLine("Object is disposed");
}
public async ValueTask DisposeAsync()
{
Console.WriteLine("Object is disposed asynchronously");
}
}
We get a compiler error here:
Quote:
error CS0246: The type or namespace name 'IAsyncDisposable' could not be found (are you missing a using directive or an assembly reference?)
The error is right. We are trying to target a recent .NET Feature in a non recent .NET Core version. But for .NET Core 3.1, such an error should not occur. We need to explain in our code which part is platform specific:
public class DummyClass
#if NETCOREAPP3_1
: IAsyncDisposable
#endif
{
public void DoSomething()
{
Console.WriteLine("Something needs is done");
}
public void Dispose()
{
Console.WriteLine("Object is disposed");
}
#if NETCOREAPP3_1
public async ValueTask DisposeAsync()
{
Console.WriteLine("Object is disposed asynchronously");
}
#endif
}
By using the #if
directive, it becomes clear that some code is .NET Core 3.1 specific. The other code can be compiled for both platforms.
Adding NuGet packages that are platform specific is also possible. This is how it works:
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.1.3" />
</ItemGroup>
Adding a Condition
ensures the right depency is resolved for the right platform. This approach works but has its issues:
- You need to manually change your csproj file.
- The more platforms you add, the more complex your csproj file becomes.
- The more platforms you add, the more complex your code becomes.
From a maintainability point of view, this may not be the situation you want. That is why an alternative for this approach needs to be explained: Shared projects with partial classes. The first step is to create a shared project, which is just a project type available in Visual Studio. In the shared project, we add a partial class that just includes code that is expected to compile for each platform we want to target. Here it is:
public partial class DummyClass : IDisposable
{
public void DoSomething()
{
Console.WriteLine("Something needs is done");
}
public void Dispose()
{
Console.WriteLine("Something is disposed");
}
}
Shared projects cannot be compiled but the code inside a shared project can be compiled. Therefore, we need to reference it in our .NET Core 2.1 project. If you have never worked with shared projects before, this is how you add such a reference. The .NET Core 2.1 project compiles since the IAsyncDisposable
interface is not there . But for the .NET Core 3.1 project, we need it. Therefore, we add a partial class there too.
public partial class DummyClass : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
Console.WriteLine("Object is disposed asynchronously");
}
}
It seems that we just resolved the mentioned issues:
- No manual changes to the project file required.
- More platforms only means more projects. The project files are all simple.
- The code files are short and simple
However, this approach has an issue too: there are more projects to maintain. Moreover, you cannot generate a NuGet package from a shared project. If you are interested in working examples for both solutions, download the code from GitHub.
Points of Interest
Shared projects are especially popular in the world of Xamarin development but can be used for other purposes too. In many companies, junior developers are told that duplicating code is a bad thing because it makes maintaining your code harder. However, what we often forget to tell them is that Shared projects has the same effect (the same code is compiled in different projects) but without its disadvantages (the code needs to be maintained in just one file being part of one project). I became a big fan of Shared Projects. However, each solution has disadvantages as I mentioned.
History
- 16th May, 2020: Initial version