This is an add-in for Fody which lets you unwraps Span<T> and ReadOnlySpan<T> in compile time.
(Note: The following configurations are intended for SDK-style projects)
-
Install the NuGet packages
FodyandSpanUnwrap.Fody. InstallingFodyexplicitly is needed to enable weaving.<PackageReference Include="Fody" Version="*"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="SpanUnwrap.Fody" Version="*"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
-
If you already have a
FodyWeavers.xmlfile in the root directory of your project, add the<SpanUnwrap />tag there. This file will be created on the first build if it doesn't exist:<?xml version="1.0" encoding="utf-8" ?> <Weavers> <SpanUnwrap /> </Weavers>
See Fody usage for general guidelines, and Fody Configuration for additional options.
If your project targets .NET Standard 2.0 (or lower), .NET Core 2.x (or lower), or .NET Framework, you must include System.Memory manually.
<PackageReference Include="System.Memory" Version="*" />Tip: Zero-Runtime Dependency: If you only need System.Memory for compile-time constant access and want to keep your runtime clean, use the following configuration instead:
<PackageReference Include="System.Memory" Version="*">
<PrivateAssets>all</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>Use the Unwrap.From() method to unwrap a Span<T> or ReadOnlySpan<T> at compile time and expose its underlying storage directly.
SpanUnwrap.Fody intercepts the call during compilation and optimizes the access based on the data source:
- For the constant
ReadOnlySpan<T>declarations: It will expose the "raw" data field, which is embedded by the compiler in your assembly, as aref readonlyreference. - For the
Span<T>fromstackalloc: It will expose the space you allocated from the stack as arefreference. - For the other cases: It will automatically route the call to the
GetPinnableReference()method in theSpan<T>orReadOnlySpan<T>instance.
What you write:
public static void CopyData(ref byte destination)
{
const int Length = 8;
CopyBlockUnaligned(
ref destination,
in Unwrap.From(
// a constant, length-sealed ReadOnlySpan<byte>.
new byte[Length] { 0xFF, 0x5E, 0x30, 0x21, 0x44, 0x55, 0x55, 0x1A }
),
Length);
}What gets compiled:
C# disassmbly:
using System.Runtime.CompilerServices;
public static void CopyData(ref byte destination)
{
CopyBlockUnaligned(ref destination, in Unsafe.As<long, byte>(
// The "raw" data field is exposed now, and can be accessed directly by your code
ref global::<PrivateImplementationDetails>.70DE15E71C9CEE3760BB7174ECC485889F6F554DDDC4947D4ECAF87E48003025)
, 8u);
}raw MSIL code:
.method public hidebysig static
void CopyData (
uint8& destination
) cil managed
{
// Method begins at RVA 0x2626
// Header size: 1
// Code size: 14 (0xe)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldsflda int64 '<PrivateImplementationDetails>'::'70DE15E71C9CEE3760BB7174ECC485889F6F554DDDC4947D4ECAF87E48003025'
IL_0006: ldc.i4.8
IL_0007: conv.i
IL_0008: call void SpanUnwrap.Example.Stores::CopyBlockUnaligned(uint8&, uint8&, native uint)
IL_000d: ret
} // end of method Stores::CopyData