Suppose you are creating a library that has a central set of features and also additional features that are only available on some platforms. This means that when the project is built there are multiple assemblies created, one for each platform.
One way to achieve multi platform targeting is to create a number of separate projects, for example one for .NET Core , one for UWP, another one for .NET framework, etc. Then a shared source code project can be added and referenced by each of these individual projects; when each project is built separate binaries are produced. I’ve used this approach in the past, for example when working on FeatureToggle but is a little clunky and results in many projects in the solution.
Another approach is to have a single project that is not limited to a single platform output, but rather compiles to multiple platform assemblies.
For example, in Visual Studio 2017, create a new .NET Core class library project called TargetingExample and add a class called WhoAmI as follows:
using System;
namespace TargetingExample
{
public static class WhoAmI
{
public static string TellMe()
{
return ".NET Core";
}
}
}
After building the following will be created: "…\MultiTargeting\TargetingExample\TargetingExample\bin\Debug\netcoreapp1.1\TargetingExample.dll". Notice the platform directory “netcoreapp1.1”.
If we add a new .NET Core console app project and reference the TargetingExample project:
using System;
using TargetingExample;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(WhoAmI.TellMe());
Console.ReadLine();
}
}
}
This produces the output: .NET Core
If we edit the FeatureToggle.csproj file it looks like the following (notice the TargetFramework element has a single value netcoreapp1.1):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
</Project>
The file can be modified as follows (notice the plural <TargetFrameworks>):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp1.1;net461;</TargetFrameworks>
</PropertyGroup>
</Project>
Building now produces: "…\MultiTargeting\TargetingExample\TargetingExample\bin\Debug\netcoreapp1.1\TargetingExample.dll" and "…\MultiTargeting\TargetingExample\TargetingExample\bin\Debug\net461\TargetingExample.dll"”.
A new Windows Classic Desktop Console App project can now be added (and the .NET framework version changed to 4.6.1) and a reference to TargetingExample added.
using System;
using TargetingExample;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(WhoAmI.TellMe());
Console.ReadLine();
}
}
}
The new console app contains the preceding code and when run produces the output: .NET Core.
Now we have a single project compiling for multiple target platforms. We can take things one step further by having different functionality depending on the target platform. One simple way to do this is to use conditional compiler directives as the following code shows:
using System;
namespace TargetingExample
{
public static class WhoAmI
{
public static string TellMe()
{
#if NETCOREAPP1_1
return ".NET Core";
#elif NETFULL
return ".NET Framework";
#else
throw new NotImplementedException(); // Safety net in case of typos in symbols
#endif
}
}
}
The preceding code relies on the conditional compilation symbols being defined, this can be done by editing the project file once again as follows:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp1.1;net461;</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">
<DefineConstants>NETCOREAPP1_1</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net461' ">
<DefineConstants>NETFULL</DefineConstants>
</PropertyGroup>
</Project>
Now when the project is built, the netcoreapp1.1\TargetingExample.dll will return “.NET Core” and net461\TargetingExample.dll will return “.NET Framework”. Each dll has been compiled with different functionality depending on the platform.
Update: The explicit <DefineConstants> for the different platforms are not required if you want to use the defaults, e.g. "NETCOREAPP1_1", "NET461", etc as per this Twitter thread and GitHub.
SHARE: