ltrzesniewski / InlineIL.Fody

Inject arbitrary IL code at compile time.
MIT License
240 stars 17 forks source link

FieldRef to generic class can not be resolved #32

Closed dotlogix closed 4 months ago

dotlogix commented 4 months ago

Hey there thx for your awesome work.

I tried to use your extension however it seems I am missing sth or there is a bug potentially. I have this code:

private static T[] GetArray<T>(this List<T> list) {
    IL.Emit.Ldarg(nameof(list));
    IL.Emit.Ldfld(FieldRef.Field(typeof(List<T>), "_items"));
    return IL.Return<T[]>();
}

However I get this error when I try to build:

Fody/InlineIL: Field '_items' not found in type System.Collections.Generic.List`1 (in T[] ConsoleApp7.Program::GetArray(System.Collections.Generic.List`1<T>) at instruction IL_0023: call System.Void InlineIL.IL/Emit::Ldfld(InlineIL.FieldRef))

I tried to do the same with a class defined in my own assembly and this worked perfectly fine. Not sure what I am missing could you assist maybe?

dotlogix commented 4 months ago

The classic "DynamicMethod" way works perfectly fine so the field name is definetly correct:

Func<List<int>, int[]> lambda;
{
    var list = Expression.Parameter(typeof(List<int>));
    var array = Expression.Field(list, typeof(List<int>).GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)!);
    lambda = Expression.Lambda<Func<List<int>, int[]>>(array, list).Compile();
}
{
    var list = new List<int> { 1, 2, 3, 4, 5, 6 };
    Console.WriteLine(string.Join(", ", lambda(list)));
}
ltrzesniewski commented 4 months ago

Thanks for the kind words! 🙂

Which framework are you targeting? I suppose it's .NET ("Core") or .NET Standard, which would mean you're not compiling against the actual implementation assembly, but a reference assembly which only contains the public API surface. That's why InlineIL can't find the implementation details such as private fields.

The DynamicMethod approach works since you have access to the implementation at that point.

You'd have to reference System.Private.CoreLib, but that brings its own problems. You may want to take a look at #31 though.

ltrzesniewski commented 4 months ago

BTW, if you want to access the underlying array of a list, maybe CollectionsMarshal.AsSpan(list) could suffice (you get a span, not an array though).

If you really need the array, code such as this should do the job, but it's not pretty:

    private static T[] GetListArray<T>(List<T> list)
        => Unsafe.As<ListLayout<T>>(list).Items;

    private class ListLayout<T>
    {
        public T[] Items = [];

#pragma warning disable CS0169 // Field is never used
        private int _size;
        private int _version;
#if NETFRAMEWORK
        private object? _syncRoot;
#endif
#pragma warning restore CS0169 // Field is never used
    }
dotlogix commented 4 months ago

Thx for your work this is helpful indeed. Basically what I need is sth like this:

ref var arrayRef = ref MemoryMarshal.GetArrayDataReference(list._items);

It is used in a very high-performance ECS. Basically all code I use there is not "pretty" so this fit's perfectly fine. Still better than the whole type erasure unmanaged pointer stuff I have to do :D

I close this as completed with this response.

mikernet commented 4 months ago

If that's all you need then isn't this better:

ref var arrayRef = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(list));

Just FYI, the _version field has a good chance of being removed soon, and then bad things could happen.