CodeGo.net>如何创建Lambda并将其添加到使用反射的动作
作者:互联网
假设在C#中,我的类具有任意数量的Action,可以具有任意数量的泛型参数:
public class Container
{
public Action a;
public Action<float> b;
public Action<int, float> c;
// etc...
}
而且我正在此类的实例上注册一些调试lambda,这些lambda仅打印出动作字段的名称:
public static void Main()
{
Container container = new Container();
container.a += () => Console.WriteLine("a was called");
container.b += (temp1) => Console.WriteLine("b was called");
container.c += (temp1, temp2) => Console.WriteLine("c was called");
container.a();
container.b(1.5f);
container.c(1, 1.5f);
}
我想使用反射自动创建这些调试lambda,如下所示:
public static void Main()
{
Container container = new Container();
GenerateDebug(container);
if(container.a != null) container.a();
if(container.b != null) container.b(1.5f);
if(container.c != null) container.c(1, 1.5f);
}
public static void GenerateDebug(Container c)
{
Type t = c.GetType();
FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public);
foreach(FieldInfo field in fields)
{
Action callback = () => Console.WriteLine(field.Name + " was called");
Type[] actionArgTypes = field.FieldType.GetGenericArguments();
if(actionArgTypes.Length == 0)
{
Action action = field.GetValue(c) as System.Action;
action += callback;
field.SetValue(c, action);
}
else
{
// 1. Create an Action<T1, T2, ...> object that takes the types in 'actionArgTypes' which wraps the 'callback' action
// 2. Add this new lambda to the current Action<T1, T2, ...> field
}
}
}
我可以在不带参数的情况下获得Actions的预期结果-上面的代码确实打印出“被调用”-但我不知道如何对泛型执行此操作.
我相信我知道我需要做什么,但不知道如何做:
>使用反射来创建动作< T1,T2,...>.使用actionArgTypes中的类型的对象,该对象包装对回调操作的调用.
>将此新创建的对象添加到由字段指定的通用操作.
我将如何去做,或者达到添加这种调试回调的预期效果的类似方法?
解决方法:
这是一个使用Expressions的相当简单的实现,可以直接使用ILGenerator,但是在这种情况下这不值得花钱.
public static void GenerateDebug(Container c)
{
Type t = c.GetType();
FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public);
foreach(FieldInfo field in fields)
{
var fieldName = field.Name;
Type[] actionArgTypes = field.FieldType.GetGenericArguments();
// Create paramter expression for each argument
var parameters = actionArgTypes.Select(Expression.Parameter).ToArray();
// Create method call expression with a constant argument
var writeLineCall = Expression.Call(typeof(Console).GetMethod("WriteLine", new [] {typeof(string)}), Expression.Constant(fieldName + " was called"));
// Create and compile lambda using the fields type
var lambda = Expression.Lambda(field.FieldType, writeLineCall, parameters);
var @delegate = lambda.Compile();
var action = field.GetValue(c) as Delegate;
// Combine and set delegates
action = Delegate.Combine(action, @delegate);
field.SetValue(c, action);
}
}
这是使用ILGenerator的相同功能,应与.net framework 2.0以及.net core一起使用.在现实生活中的应用程序中,应该进行检查,缓存以及可能是整个assemblybuilder:
public static void GenerateDebug(Container c)
{
Type t = c.GetType();
FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public);
foreach(FieldInfo field in fields)
{
var fieldName = field.Name;
Type[] actionArgTypes = field.FieldType.GetGenericArguments();
var dm = new DynamicMethod(fieldName, typeof(void), actionArgTypes);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldstr, fieldName + " was called using ilgen");
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new [] {typeof(string)}));
il.Emit(OpCodes.Ret);
var @delegate = dm.CreateDelegate(field.FieldType);
var action = field.GetValue(c) as Delegate;
// Combine and set delegates
action = Delegate.Combine(action, @delegate);
field.SetValue(c, action);
}
}
标签:system-reflection,reflection,delegates,c,lambda 来源: https://codeday.me/bug/20191108/2008854.html