Skip to content

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

 
 
 

Contributors