Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ILAccess.Fody provides functionality similar to the UnsafeAccessor introduced in .NET 8, but supports older .NET platforms.

License

Notifications You must be signed in to change notification settings

huoshan12345/ILAccess.Fody

Repository files navigation

ILAccess.Fody

Build NuGet package .net License 中文

✨ 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 Fody and ILAccess.Fody NuGet packages with a PrivateAssets="all" attribute on their <PackageReference /> items. Installing Fody explicitly is needed to enable weaving.

    <PackageReference Include="Fody" Version="..." PrivateAssets="all" />
    <PackageReference Include="ILAccess.Fody" Version="..." PrivateAssets="all" />
  • If you already have a FodyWeavers.xml file 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.

About

ILAccess.Fody provides functionality similar to the UnsafeAccessor introduced in .NET 8, but supports older .NET platforms.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published