プロパティにはGetGetMethod
とGetSetMethod
があるので、次のことができます。
Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>),
propertyInfo.GetGetMethod());
そして
Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>),
propertyInfo.GetSetMethod());
しかし、どうすればFieldInfo
sを実行できますか?
GetValue
とSetValue
のデリゲートを探していません(つまり、毎回リフレクションを呼び出します)
Getter = s => (T)fieldInfo.GetValue(s);
Setter = (s, t) => (T)fieldInfo.SetValue(s, t);
しかし、ここにCreateDelegate
アプローチがある場合はどうでしょうか? つまり 割り当ては値を返すので 、割り当てをメソッドのように扱うことはできますか?もしそうなら、MethodInfo
それのハンドル?言い換えると、メンバーフィールドから値を設定して取得する正しいMethodInfo
をCreateDelegate
メソッドに渡して、デリゲートを取得するにはどうすればよいですか?フィールドに直接読み書きできるのはどれですか?
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();
}
それとも私は存在しない後ですか(私はまだそのようなものを見たことがないので)?
フィールドアクセスは、メソッド(getterやsetterなど)を介して実行されるのではなく、IL命令を使用して実行されるため、デリゲートにできることは何もありませんassign。式ルートを使用して、デリゲートに割り当てることができるコードの「ブロック」(事実上IL)を作成する必要があります。
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>
は、最初の引数として値で渡された場合、構造体のメンバーを変更するために使用することはできません。
フィールドを取得/設定するためのデリゲートを作成する簡単な方法はありません。
その機能を提供するには、独自のコードを作成する必要があります。これを提供するために、共有ライブラリに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を使用してこれらのデリゲートを作成することもできますが、そのコードはより複雑になり、パフォーマンスが向上することはありません。
Expression
を使用するかどうかはわかりませんが、なぜリフレクションを回避するのですか? Expression
のほとんどの操作は、リフレクションに依存しています。
GetValue
とSetValue
自体はフィールドのget method
とset method
ですが、特定のフィールド用ではありません。
フィールドはプロパティとは異なり、フィールドであり、それぞれのget/setメソッドを生成する理由はありません。ただし、タイプはフィールドによって異なる場合があるため、GetValue
とSetValue
は、差異のためにparameter/return value
をobject
として定義されます。 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氏のブログ。
オブジェクトを操作しているときにデリゲートを作成するための別のオプションを次に示します(フィールドの特定のタイプがわからない)。フィールドが構造体の場合は遅くなりますが(ボクシングのため)。
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>));
}
}
DynamicMethod
のref 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
それを行う方法をさらに追加するだけです: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()));
}