System.Memory exists to provide the
Memory<T> and similar types, for which C# 7.x provided support, and it can enable significant performance improvements where appropriate. While .NET Standard 2.0 does not have support for it in its available API surface, there the
System.Memory NuGet package that can be added to enable it.
As part of the code sharing effort, Mono and Xamarin added support for
System.Memory in the BCL as part of the releases bundled with VS 2019.
While this is a very important step to make Mono and .NET Core very similar in behavior, this brings issues because the
MonoAndroid target frameworks are not versioned like .NET Core or .NET Standard. The Visual Studio version used to build a project has an impact on the code’s behavior or compatibility that cannot be adjusted as easily as changing a Target Framework version.
In VS2019, the following code :
var s = new Span<int>();
with a project file like this:
<Project Sdk="MSBuild.Sdk.Extras/1.6.46"> <PropertyGroup> <TargetFrameworks>xamarinios10;monoandroid90</TargetFrameworks> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Memory" Version="4.5.2"/> </ItemGroup> </Project>
does not build in
Xamarin.iOS10, giving this error:
error CS0433: The type 'Span<T>' exists in both
'System.Memory, Version=126.96.36.199, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' and
'mscorlib, Version=188.8.131.52, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'
One easy fix would be to remove the
System.Memory package because
mscorlib already provides it, but it would prevent this library from building with VS15.9 and below.
We could also only add
System.Memory as a normal assembly reference, which would work on both VS versions, but it would break the NuGet package creation and make the consumption of the project’s resulting NuGet package difficult.
Adjusting references during the build
In recent MSBuild, there is a new
MSBuildVersion property which tells the currently running version. While this is far from perfect, we can assume that when a given MSBuild version is installed at the same time as Xamarin for an installation of Visual Studio, the MSBuild version is implicitly tied to the installed Xamarin version.
To keep the
PackageReference visible for the NuGet packing, but remove the assembly reference added by the package, only in VS16.x, we can inject an MSBuild target:
<Project Sdk="MSBuild.Sdk.Extras/1.6.46"> <PropertyGroup> <TargetFrameworks>xamarinios10</TargetFrameworks> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Memory" Version="4.5.2"/> </ItemGroup> <Target Name="VS16_RemoveSystemMemory" BeforeTargets="FindReferenceAssembliesForReferences" Condition="'$(MSBuildVersion)' >= '16.0'"> <ItemGroup> <_ReferencePathToRemove Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='System.Memory'" /> <ReferencePath Remove="@(_ReferencePathToRemove)" /> </ItemGroup> <Message Text="Removing System.Memory for VS 2019 compatibility" Importance="high"/> </Target> </Project>
'$(MSBuildVersion)' >= '16.0' condition ensure that the target only runs when VS 16.0 is used to build the project. Then we remove the
ReferencePath item that is generated by the loading of the
<PackageReference Include="System.Memory" />.
The target is injected automatically before the
FindReferenceAssembliesForReferences that can be found by looking at the generated binary log, and searching where the
ReferencePath values are manipulated to be provided to the CSC task.
Interestingly enough, this manipulation does not need to be made in Xamarin.iOS head projects, where the reference seems to be automatically adjusted to be removed by the existing targets.