简体   繁体   English

为任何方法创建 Func 或 Action(在 C# 中使用反射)

[英]Create Func or Action for any method (using reflection in c#)

My application works with loading dll's dynamically, based on settings from the database (file, class and method names).我的应用程序根据数据库的设置(文件、class 和方法名称)动态加载 dll。 To facilitate, expedite and reduce the use of reflection I would like to have a cache....为了方便、加快和减少反射的使用,我想有一个缓存....

Following the idea that using:遵循使用的想法:

 MethodInfo.Invoke

Is nothing performative ( Reflection Performance - Create Delegate (Properties C#) ) I would like to translate any call to methods.没有任何表现( 反射性能 - 创建委托(Properties C#) )我想翻译对方法的任何调用。 I thought of something that would work like this:我想到了一些可以像这样工作的东西:

public static T Create<T>(Type type, string methodName) // or
public static T Create<T>(MethodInfo info) // to use like this:
var action = Create<Action<object>>(typeof(Foo), "AnySetValue");

One requirement is that all the parameters, can be object.一个要求是所有的参数,可以是object。

I'm trying to deal with expressions, and so far I have something like this:我正在尝试处理表达式,到目前为止我有这样的事情:

    private void Sample()
    {
        var assembly = Assembly.GetAssembly(typeof(Foo));

        Type customType = assembly.GetType("Foo");

        var actionMethodInfo = customType.GetMethod("AnyMethod");
        var funcMethodInfo = customType.GetMethod("AnyGetString");
        var otherActionMethod = customType.GetMethod("AnySetValue");
        var otherFuncMethodInfo = customType.GetMethod("OtherGetString");

        var foo = Activator.CreateInstance(customType);
        var actionAccessor = (Action<object>)BuildSimpleAction(actionMethodInfo);
        actionAccessor(foo);

        var otherAction = (Action<object, object>)BuildOtherAction(otherActionMethod);
        otherAction(foo, string.Empty);

        var otherFuncAccessor = (Func<object, object>)BuildFuncAccessor(funcMethodInfo);
        otherFuncAccessor(foo);

        var funcAccessor = (Func<object,object,object>)BuildOtherFuncAccessor(otherFuncMethodInfo);
        funcAccessor(foo, string.Empty);
    }

    static Action<object> BuildSimpleAction(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");

        Expression<Action<object>> expr =
            Expression.Lambda<Action<object>>(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method), obj);

        return expr.Compile();
    }

    static Func<object, object> BuildFuncAccessor(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");

        Expression<Func<object, object>> expr =
            Expression.Lambda<Func<object, object>>(
                Expression.Convert(
                    Expression.Call(
                        Expression.Convert(obj, method.DeclaringType),
                        method),
                    typeof(object)),
                obj);

        return expr.Compile();

    }

    static Func<object, object, object> BuildOtherFuncAccessor(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");
        var value = Expression.Parameter(typeof(object));

        Expression<Func<object, object, object>> expr =
            Expression.Lambda<Func<object, object, object>>(
                    Expression.Call(
                        Expression.Convert(obj, method.DeclaringType),
                        method,
                        Expression.Convert(value, method.GetParameters()[0].ParameterType)), 
                        obj, value);

        return expr.Compile();

    }

    static Action<object, object> BuildOtherAction(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");
        var value = Expression.Parameter(typeof(object));

        Expression<Action<object, object>> expr =
            Expression.Lambda<Action<object, object>>(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method,
                    Expression.Convert(value, method.GetParameters()[0].ParameterType)),
                obj,
                value);

        return expr.Compile();
    }

public class Foo
{
    public void AnyMethod() {}

    public void AnySetValue(string value) {}

    public string AnyGetString()
    {            return string.Empty;        }

    public string OtherGetString(string value)
    {            return string.Empty;        }
}

Is there any way to simplify this code?有什么办法可以简化这段代码吗? (I believe it is possible to create a method only using generic..) And when you have 3, 4, 5, any parameters as I do? (我相信可以只使用泛型创建一个方法。)当你有 3、4、5 时,任何参数都像我一样?


I was thinking, what if there was something like this:我在想,如果有这样的事情怎么办:

https://codereview.stackexchange.com/questions/1070/generic-advanced-delegate-createdelegate-using-expression-trees https://codereview.stackexchange.com/questions/1070/generic-advanced-delegate-createdelegate-using-expression-trees

but I'll have more one parameter (in action or function), this parameter (first parameter) an object to perform.但是我会有更多的一个参数(在动作或函数中),这个参数(第一个参数)一个 object 来执行。 Is this possible?这可能吗?

I have made a sample program that fulfills all your requirements (I think!) 我已经制作了一个满足您所有要求的示例程序(我想!)

class Program
{
    class MyType
    {
        public MyType(int i) { this.Value = i; }

        public void SetValue(int i) { this.Value = i; }

        public void SetSumValue(int a, int b) { this.Value = a + b; }

        public int Value { get; set; }
    }

    public static void Main()
    {
        Type type = typeof(MyType);

        var mi = type.GetMethod("SetValue");

        var obj1 = new MyType(1);
        var obj2 = new MyType(2);

        var action = DelegateBuilder.BuildDelegate<Action<object, int>>(mi);

        action(obj1, 3);
        action(obj2, 4);

        Console.WriteLine(obj1.Value);
        Console.WriteLine(obj2.Value);

        // Sample passing a default value for the 2nd param of SetSumValue.
        var mi2 = type.GetMethod("SetSumValue");

        var action2 = DelegateBuilder.BuildDelegate<Action<object, int>>(mi2, 10);

        action2(obj1, 3);
        action2(obj2, 4);

        Console.WriteLine(obj1.Value);
        Console.WriteLine(obj2.Value);

        // Sample without passing a default value for the 2nd param of SetSumValue.
        // It will just use the default int value that is 0.
        var action3 = DelegateBuilder.BuildDelegate<Action<object, int>>(mi2);

        action3(obj1, 3);
        action3(obj2, 4);

        Console.WriteLine(obj1.Value);
        Console.WriteLine(obj2.Value);
    }
}

DelegateBuilder class: DelegateBuilder类:

public class DelegateBuilder
{
    public static T BuildDelegate<T>(MethodInfo method, params object[] missingParamValues)
    {
        var queueMissingParams = new Queue<object>(missingParamValues);

        var dgtMi = typeof(T).GetMethod("Invoke");
        var dgtRet = dgtMi.ReturnType;
        var dgtParams = dgtMi.GetParameters();

        var paramsOfDelegate = dgtParams
            .Select(tp => Expression.Parameter(tp.ParameterType, tp.Name))
            .ToArray();

        var methodParams = method.GetParameters();

        if (method.IsStatic)
        {
            var paramsToPass = methodParams
                .Select((p, i) => CreateParam(paramsOfDelegate, i, p, queueMissingParams))
                .ToArray();

            var expr = Expression.Lambda<T>(
                Expression.Call(method, paramsToPass),
                paramsOfDelegate);

            return expr.Compile();
        }
        else
        {
            var paramThis = Expression.Convert(paramsOfDelegate[0], method.DeclaringType);

            var paramsToPass = methodParams
                .Select((p, i) => CreateParam(paramsOfDelegate, i + 1, p, queueMissingParams))
                .ToArray();

            var expr = Expression.Lambda<T>(
                Expression.Call(paramThis, method, paramsToPass),
                paramsOfDelegate);

            return expr.Compile();
        }
    }

    private static Expression CreateParam(ParameterExpression[] paramsOfDelegate, int i, ParameterInfo callParamType, Queue<object> queueMissingParams)
    {
        if (i < paramsOfDelegate.Length)
            return Expression.Convert(paramsOfDelegate[i], callParamType.ParameterType);

        if (queueMissingParams.Count > 0)
            return Expression.Constant(queueMissingParams.Dequeue());

        if (callParamType.ParameterType.IsValueType)
            return Expression.Constant(Activator.CreateInstance(callParamType.ParameterType));

        return Expression.Constant(null);
    }
}

How it works 这个怎么运作

The core is the BuildDelegate method: 核心是BuildDelegate方法:

static T BuildDelegate<T>(MethodInfo method)

  • T is the delegate type you want to create. T是您要创建的委托类型。
  • method is the MethodInfo of the method you want to be called by the generated delegate. method是您希望由生成的委托调用的方法的MethodInfo。

Example call: var action = BuildDelegate<Action<object, int>>(mi); 示例调用: var action = BuildDelegate<Action<object, int>>(mi);

Rules for parameters: 参数规则:

  • If the method passed is an instance method, first parameter of the generated delegate will accept the instance of the object, that contains the method itself. 如果传递的方法是实例方法,则生成的委托的第一个参数将接受包含方法本身的对象的实例。 All other parameter will be passed to the method. 所有其他参数将传递给该方法。

  • If the method passed is a static method, then all parameters of the generated delegate will be passed to the method. 如果传递的方法是静态方法,则生成的委托的所有参数都将传递给方法。

  • Missing parameters will have default values passed. 缺少参数将传递默认值。

  • Extra parameters will be discarded. 额外的参数将被丢弃。

Delegate.CreateDelegate is much simpler than building expression trees. Delegate.CreateDelegate比构建表达式树简单得多。

    var assembly = Assembly.GetAssembly(typeof(Foo));

    Type customType = assembly.GetType("Foo");

    var actionMethodInfo = customType.GetMethod("AnyMethod");

    var foo = Activator.CreateInstance(customType);

    Action action = (Action)Delegate.CreateDelegate(typeof(Action), foo, actionMethodInfo);

I've ran into a very similar scenario and I've found the following to work very well. 我遇到了一个非常类似的场景,我发现以下内容非常有效。

First, let's set up our test Foo class: 首先,让我们设置我们的测试Foo类:

public class Foo
{
    private string _name;

    public Foo(string name)
    {
        _name = name;
    }

    public void AnyMethod()
    {
        Console.WriteLine("{0} Called: AnyMethod()", _name);
    }

    public void AnySetValue(string value)
    {
        Console.WriteLine("{0} Called: AnySetValue(string) with {1}", _name, value);
    }

    public string AnySetString(string value)
    {
        Console.WriteLine("{0} Called: AnySetString(string) with {1}", _name, value);
        return value;
    }
}

Next, we create a set of methods to create our reusable method wrappers: 接下来,我们创建一组方法来创建可重用的方法包装器:

public static Action<object> CreateReusableAction<TClass>(string methodName)
{
    var method = typeof(TClass).GetMethod(methodName);
    var del = Delegate.CreateDelegate(typeof(Action<TClass>), method);
    Action<object> caller = (instance) => del.DynamicInvoke(instance);
    return caller;
}

public static Action<object, object> CreateReusableAction<TClass, TParam1>(string methodName)
{
    var method = typeof(TClass).GetMethod(methodName, new Type[] { typeof(TParam1) });
    var del = Delegate.CreateDelegate(typeof(Action<TClass, TParam1>), method);
    Action<object, object> caller = (instance, param) => del.DynamicInvoke(instance, param);
    return caller;
}

public static Func<object, object, object> CreateReusableFunction<TClass, TParam1, TReturn>(string methodName)
{
    var method = typeof(TClass).GetMethod(methodName, new Type[] { typeof(TParam1) });
    var del = Delegate.CreateDelegate(typeof(Func<TClass, TParam1, TReturn>), method);
    Func<object, object, object> caller = (instance, param) => (TReturn)del.DynamicInvoke(instance, param);
    return caller;
}

Then we can use it as follows: 然后我们可以使用它如下:

var myFoo = new Foo("myFoo");
var otherFoo = new Foo("otherFoo");

var anyMethod = CreateReusableAction<Foo>("AnyMethod");
anyMethod(myFoo);
anyMethod(otherFoo);

var anySetValue = CreateReusableAction<Foo, string>("AnySetValue");
anySetValue(myFoo, "Value 1");
anySetValue(otherFoo, "Value 2");

var anySetString = CreateReusableFunction<Foo, string, string>("AnySetString");
var firstResult = anySetString(myFoo, "MyFooValue1");
var secondResult = anySetString(otherFoo, "OtherFooValue1");

Which produces the output: 产生输出:

myFoo Called: AnyMethod()
otherFoo Called: AnyMethod()
myFoo Called: AnySetValue(string) with Value 1
otherFoo Called: AnySetValue(string) with Value 2
myFoo Called: AnySetString(string) with MyFooValue1
otherFoo Called: AnySetString(string) with OtherFooValue1

If you can use libraries in your project, try the Impromptu Interfaces (available here: http://code.google.com/p/impromptu-interface/ ) or on nuget. 如果您可以在项目中使用库,请尝试使用Impromptu接口(可在此处获取: http//code.google.com/p/impromptu-interface/ )或nuget。

This has a lot of functionality for handling reflection on types and also implements internal caching. 这具有许多用于处理类型反射的功能,还实现了内部缓存。 I've been using it in a project that relies heavily on reflection and the performance is really good. 我一直在一个严重依赖反射的项目中使用它,性能非常好。

The library itself has a lot of functionality but it also handles scenarios like yours. 库本身有很多功能,但它也可以处理像你这样的场景。

I'm not sure, but why you don't use the dynamic type? 我不确定,但为什么你不使用dynamic类型? For example: 例如:

dynamic foo = Activator.CreateInstance(customType);
foo.AnyMethod();
foo.AnySetValue("test string");
foo.OtherMethod("method", "with", "many", "parameters");

I'm a bit unsure what you are really trying to do, but maybe this helps? 我有点不确定你真正想要做什么,但也许这有帮助吗?

    public static Func<object, object[], object> CreateDelegate(MethodInfo method)
    {
        return new Func<object, object[], object>((object instance, object[] args) => method.Invoke(instance, args));
    }
    // Merely providing syntactic suger, ie able to write 
    // method.MyInvoke(instance, arg1, arg2, arg3, arg4) 
    // instead of having to put the args in an array, ie Invoke(instance, new object[]{arg1, arg2}) etc
    public static object MyInvoke(this Func<object, object[], object> func, object instance, params object[] args)
    {
        return func(instance, args);
    }

    public static void TestCode()
    {
        var method = typeof(string).GetMethod("get_Length");

        var wrappfunc = CreateDelegate(method);
        // Calling get_Length (ie Length property) on the string "klasjf"
        wrappfunc.MyInvoke("klasjf"); 
    }

You will lose the strong typing though, since all the arguments are object , and I am a bit unsure what you really are trying to accomplish. 你会失去强大的输入,因为所有的参数都是object ,而我有点不确定你真正要完成的是什么。

I found a simple way to to do this for my ICommand factory I'm building:我找到了一种简单的方法来为我正在构建的 ICommand 工厂执行此操作:

public virtual ObjectCommand FromMethodName(string methodName)
{
    var t = typeof(T); // Inherited from generic class this factory resides within
    var m = t.GetMethod(methodName, Array.Empty<Type>());
    Action a = new Action(() =>
       {
          m.Invoke(ViewModel.ObjectModel, null); // Invoke the method name against the ViewModel's current object
       });
    return new ObjectCommand(this.ViewModel, a);
}

you can then create the action using the following, even works against a null object reference:然后您可以使用以下内容创建操作,甚至可以针对 null object 参考进行操作:

TestCommand = this.CommandFactory.FromMethodName(nameof(this.ObjectModel.SetResult));

As the reference to the ObjectModel changed, the result will change as well since its being invoked against the current reference随着对 ObjectModel 的引用发生变化,结果也会发生变化,因为它是针对当前引用调用的

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM