System.Memory exists to provide the Span<T>, 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 Xamarin.iOS, Xamarin.Mac and 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.

VS2019 issues

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=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' and 'mscorlib, Version=2.0.5.0, 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)' &gt;= '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>

The '$(MSBuildVersion)' &gt;= '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.