web-dev-qa-db-ja.com

FieldInfoの値を取得および設定するためのデリゲートを作成する方法はありますか?

プロパティにはGetGetMethodGetSetMethodがあるので、次のことができます。

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), 
                                             propertyInfo.GetGetMethod());

そして

Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), 
                                               propertyInfo.GetSetMethod());

しかし、どうすればFieldInfosを実行できますか?

GetValueSetValueのデリゲートを探していません(つまり、毎回リフレクションを呼び出します)

Getter = s => (T)fieldInfo.GetValue(s);
Setter = (s, t) => (T)fieldInfo.SetValue(s, t);

しかし、ここにCreateDelegateアプローチがある場合はどうでしょうか? つまり 割り当ては値を返すので 、割り当てをメソッドのように扱うことはできますか?もしそうなら、MethodInfoそれのハンドル?言い換えると、メンバーフィールドから値を設定して取得する正しいMethodInfoCreateDelegateメソッドに渡して、デリゲートを取得するにはどうすればよいですか?フィールドに直接読み書きできるのはどれですか?

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), fieldInfo.??);
Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), fieldInfo.??);

式を作成してコンパイルすることはできますが、もっと簡単なものを探しています。結局、以下に示すように、尋ねられた質問に対する答えがない場合、私は表現ルートに行くことを気にしません:

var instExp = Expression.Parameter(typeof(S));
var fieldExp = Expression.Field(instExp, fieldInfo);
Getter = Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile();
if (!fieldInfo.IsInitOnly)
{
    var valueExp = Expression.Parameter(typeof(T));
    Setter = Expression.Lambda<Action<S, T>>(Expression.Assign(fieldExp, valueExp), instExp, valueExp).Compile();
}

それとも私は存在しない後ですか(私はまだそのようなものを見たことがないので)?

21
nawfal

フィールドアクセスは、メソッド(getterやsetterなど)を介して実行されるのではなく、IL命令を使用して実行されるため、デリゲートにできることは何もありませんassign。式ルートを使用して、デリゲートに割り当てることができるコードの「ブロック」(事実上IL)を作成する必要があります。

10
Peter Ritchie

Peter Ritchieが提案したように、実行時に独自のコードをコンパイルできます。メソッドは、デリゲートを初めて呼び出すとすぐにコンパイルされます。したがって、最初の呼び出しは遅くなりますが、その後の呼び出しは、アンマネージポインター/ユニオンなしで.NETで取得できるのと同じくらい速くなります。最初の呼び出しを除いて、デリゲートは直接FieldInfoよりも約500倍高速です。

class DemoProgram
{
    class Target
    {
        private int value;
    }

    static void Main(string[] args)
    {
        FieldInfo valueField = typeof(Target).GetFields(BindingFlags.NonPublic| BindingFlags.Instance).First();
        var getValue = CreateGetter<Target, int>(valueField);
        var setValue = CreateSetter<Target, int>(valueField);

        Target target = new Target();

        setValue(target, 42);
        Console.WriteLine(getValue(target));
    }

    static Func<S, T> CreateGetter<S, T>(FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(T), new Type[1] { typeof(S) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldsfld, field);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, field);
        }
        gen.Emit(OpCodes.Ret);
        return (Func<S, T>)setterMethod.CreateDelegate(typeof(Func<S, T>));
    }

    static Action<S, T> CreateSetter<S,T>(FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName+".set_"+field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2]{typeof(S),typeof(T)},true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stsfld, field);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stfld, field);
        }
        gen.Emit(OpCodes.Ret);
        return (Action<S, T>)setterMethod.CreateDelegate(typeof(Action<S, T>));
    }
}

構造体は値によって渡されることに注意してください。つまり、Action<S, T>は、最初の引数として値で渡された場合、構造体のメンバーを変更するために使用することはできません。

28
Zotta

フィールドを取得/設定するためのデリゲートを作成する簡単な方法はありません。

その機能を提供するには、独自のコードを作成する必要があります。これを提供するために、共有ライブラリに2つの関数を提案します。

コードの使用(この例では、get-delegateの作成のみを示しています):

static public class FieldInfoExtensions
{
    static public Func<S, T> CreateGetFieldDelegate<S, T>(this FieldInfo fieldInfo)
    {
        var instExp = Expression.Parameter(typeof(S));
        var fieldExp = Expression.Field(instExp, fieldInfo);
        return Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile();
    }
}

これにより、FieldInfoからget-delegateを簡単に作成できます(フィールドがint型であると想定)。

Func<MyClass, int> getter = typeof(MyClass).GetField("MyField").CreateGetFieldDelegate<MyClass, int>();

または、コードを少し変更した場合:

static public class TypeExtensions
{
    static public Func<S, T> CreateGetFieldDelegate<S, T>(this Type type, string fieldName)
    {
        var instExp = Expression.Parameter(type);
        var fieldExp = Expression.Field(instExp, fieldName);
        return Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile();
    }
}

これにより、さらに簡単になります。

Func<MyClass, int> getter = typeof(MyClass).CreateGetFieldDelegate<MyClass, int>("MyField");

ILを使用してこれらのデリゲートを作成することもできますが、そのコードはより複雑になり、パフォーマンスが向上することはありません。

10
Martin Mulder

Expressionを使用するかどうかはわかりませんが、なぜリフレクションを回避するのですか? Expressionのほとんどの操作は、リフレクションに依存しています。

GetValueSetValue自体はフィールドのget methodset methodですが、特定のフィールド用ではありません。

フィールドはプロパティとは異なり、フィールドであり、それぞれのget/setメソッドを生成する理由はありません。ただし、タイプはフィールドによって異なる場合があるため、GetValueSetValueは、差異のためにparameter/return valueobjectとして定義されます。 GetValueは抽象メソッドです。つまり、それをオーバーライドするすべてのクラス(まだリフレクション)は、同一のシグニチャー内にある必要があります。

それらをtypeしない場合は、次のコードで実行できます。

public static void SomeMethod(FieldInfo fieldInfo) {
    var Getter=(Func<object, object>)fieldInfo.GetValue;
    var Setter=(Action<object, object>)fieldInfo.SetValue;
}

ただし、必要に応じて、制約のある方法があります。

public static void SomeMethod<S, T>(FieldInfo fieldInfo)
    where S: class
    where T: class {
    var Getter=(Func<S, object>)fieldInfo.GetValue;
    var Setter=(Action<S, T>)fieldInfo.SetValue;
}

GetterがまだFunc<S, object>であるという理由で、次のように表示することをお勧めします。

C#の共変性と反変性、パート3:メソッドグループ変換分散 Lippert氏のブログ。

2
Ken Kin

オブジェクトを操作しているときにデリゲートを作成するための別のオプションを次に示します(フィールドの特定のタイプがわからない)。フィールドが構造体の場合は遅くなりますが(ボクシングのため)。

public static class ReflectionUtility
{
    public static Func<object, object> CompileGetter(this FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(object), new[] { typeof(object) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldsfld, field);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Castclass, field.DeclaringType);
            gen.Emit(OpCodes.Ldfld, field);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType);
        }
        gen.Emit(OpCodes.Ret);
        return (Func<object, object>)setterMethod.CreateDelegate(typeof(Func<object, object>));
    }

    public static Action<object, object> CompileSetter(this FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".set_" + field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, null, new[] { typeof(object), typeof(object) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType);
            gen.Emit(OpCodes.Stsfld, field);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Castclass, field.DeclaringType);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType);
            gen.Emit(OpCodes.Stfld, field);
        }
        gen.Emit(OpCodes.Ret);
        return (Action<object, object>)setterMethod.CreateDelegate(typeof(Action<object, object>));
    }
}
1

DynamicMethodref return制限はなくなったようです少なくともv4.0.30319では

変更 Glenn Slaydenによるこの回答 情報付き Microsoftドキュメントから

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApp {
    public class MyClass {
        private int privateInt = 6;
    }

    internal static class Program {
        private delegate ref TReturn OneParameter<TReturn, in TParameter0>(TParameter0 p0);

        private static void Main() {
            var myClass = new MyClass();

            var method = new DynamicMethod(
                "methodName",
                typeof(int).MakeByRefType(), // <- MakeByRefType() here
                new[] {typeof(MyClass)}, 
                typeof(MyClass), 
                true); // skip visibility


            const BindingFlags bindings = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

            var il = method.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldflda, typeof(MyClass).GetField("privateInt", bindings));
            il.Emit(OpCodes.Ret);

            var getPrivateInt = (OneParameter<int, MyClass>) method.CreateDelegate(typeof(OneParameter<int, MyClass>));

            Console.WriteLine(typeof(string).Assembly.ImageRuntimeVersion);
            Console.WriteLine(getPrivateInt(myClass));
        }
    }
}

出力:

6
1

それを行う方法をさらに追加するだけです:D

 public static Func<T, TResult> CreatePropertyOrFieldReaderDelegate<T, TResult>(string field)
        {
            var input = Expression.Parameter(typeof(T));
            return Expression.Lambda<Func<T, TResult>>(Expression.PropertyOrField(input, field), input).Compile();
        }

これにより、値を返すメソッドが作成されます。

テストケース

class Testing {
  public int Data = 2;
  public string PropData { get; } = "Default";
 }


  [Fact]
  public void CreateSingleFieldReader()
        {
            var a = ReflectionHelper.CreatePropertyOrFieldReaderDelegate<Testing, int>("Data");
            Assert.Equal(2, a(new Testing()));

        }
0
PEtter