ILAccess.Fody
0.1.4
dotnet add package ILAccess.Fody --version 0.1.4
NuGet\Install-Package ILAccess.Fody -Version 0.1.4
<PackageReference Include="ILAccess.Fody" Version="0.1.4" />
<PackageVersion Include="ILAccess.Fody" Version="0.1.4" />
<PackageReference Include="ILAccess.Fody" />
paket add ILAccess.Fody --version 0.1.4
#r "nuget: ILAccess.Fody, 0.1.4"
#:package ILAccess.Fody@0.1.4
#addin nuget:?package=ILAccess.Fody&version=0.1.4
#tool nuget:?package=ILAccess.Fody&version=0.1.4
ILAccess.Fody
β¨ Overview
ILAccess.Fody provides functionality similar to the UnsafeAccessor introduced in .NET 8, but supports older .NET platforms. It is a Fody weaver that injects IL at compile-time, enabling access to private or internal members without runtime reflection. This results in faster access and compile-time safety compared to traditional reflection-based approaches.
π Installation
Include the
FodyandILAccess.FodyNuGet packages with aPrivateAssets="all"attribute on their<PackageReference />items. InstallingFodyexplicitly is needed to enable weaving.<PackageReference Include="Fody" Version="..." PrivateAssets="all" /> <PackageReference Include="ILAccess.Fody" Version="..." PrivateAssets="all" />If you already have a
FodyWeavers.xmlfile in the root directory of your project, add the<ILAccess />tag there. This file will be created on the first build if it doesn't exist:<?xml version="1.0" encoding="utf-8" ?> <Weavers> <ILAccess /> </Weavers>
See Fody usage for general guidelines, and Fody Configuration for additional options.
π§© Usage Example
You can use ILAccessor to access private fields, methods, or constructors β similar to UnsafeAccessor since .NET 8.
public class TestModel
{
private static int _staticValue = 42;
private int _value;
private TestModel(int value) => _value = value;
private string GetMessage(int code)
=> $"Current value: {_value}, code: {code}";
private static string GetStaticMessage(int code)
=> $"Current static value: {_staticValue}, code: {code}";
}
public static class Accessors
{
[ILAccessor(ILAccessorKind.Field, Name = "_value")]
public static extern ref int Value(this TestModel instance);
[ILAccessor(ILAccessorKind.StaticField, Name = "_staticValue")]
public static extern ref int StaticValue(TestModel instance);
[ILAccessor(ILAccessorKind.Method, Name = "GetMessage")]
public static extern string GetMessage(this TestModel instance, int code);
[ILAccessor(ILAccessorKind.StaticMethod, Name = "GetStaticMessage")]
public static extern string GetStaticMessage(TestModel? instance, int code);
[ILAccessor(ILAccessorKind.Constructor)]
public static extern TestModel Ctor(int x);
}
internal class Program
{
private static void Main(string[] args)
{
var model = Ctor(100);
ref var value = ref model.Value();
Console.WriteLine($"_value: {value}");
value += 50;
Console.WriteLine($"_value updated: {value}");
ref var staticValue = ref StaticValue(model);
Console.WriteLine($"_staticValue: {staticValue}");
staticValue += 10;
Console.WriteLine($"_staticValue updated: {staticValue}");
var message = model.GetMessage(7);
Console.WriteLine($"GetMessage: {message}");
var staticMessage = GetStaticMessage(null, 7);
Console.WriteLine($"GetStaticMessage: {staticMessage}");
Console.Read();
}
}
π οΈ How It Works
The stub methods in the TestModel are replaced at compile-time with injected IL instructions that directly access the target members.
Below is an example of what the generated IL looks like after weaving:
.method public hidebysig static int32& Value(class ILAccess.Example.TestModel 'instance') cil managed
{
IL_0000: ldarg.0 // 'instance'
IL_0001: ldflda int32 ILAccess.Example.TestModel::_value
IL_0006: ret
}
.method public hidebysig static int32& StaticValue(class ILAccess.Example.TestModel 'instance') cil managed
{
IL_0000: ldsflda int32 ILAccess.Example.TestModel::_staticValue
IL_0005: ret
}
.method public hidebysig static string GetMessage(class ILAccess.Example.TestModel 'instance', int32 code) cil managed
{
IL_0000: ldarg.0 // 'instance'
IL_0001: ldarg.1 // code
IL_0002: callvirt instance string ILAccess.Example.TestModel::GetMessage(int32)
IL_0007: ret
}
.method public hidebysig static string GetStaticMessage(class ILAccess.Example.TestModel 'instance', int32 code) cil managed
{
IL_0000: ldarg.1 // code
IL_0001: call string ILAccess.Example.TestModel::GetStaticMessage(int32)
IL_0006: ret
}
.method public hidebysig static class ILAccess.Example.TestModel Ctor(int32 x) cil managed
{
IL_0000: ldarg.0 // x
IL_0001: newobj instance void ILAccess.Example.TestModel::.ctor(int32)
IL_0006: ret
}
These injected method bodies effectively make private and static members accessible in a strongly-typed, reflection-free way.
βοΈ Comparison
| Feature | Reflection | UnsafeAccessor | ILAccess.Fody |
|---|---|---|---|
| Performance | Slow π | Fast π | Fast π |
| Works before .NET 8 | β | β | β |
| Compile-time validation | β | β | β |
| AOT | Partly supported β οΈ | β | β |
π§ Todo
- Add more test cases.
- Add more compile-time validation and diagnostic messages.
π License
MIT License β see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Fody (>= 6.9.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.