【问题标题】:Invoke method with generic expression and action as parameters以泛型表达式和动作作为参数调用方法
【发布时间】:2024-04-25 20:10:01
【问题描述】:

我需要调用如下所示的方法:

public bool DoSomething<TEntity>(Expression<Func<TEntity, bool>> expression, Action<TEntity> action) where TEntity : class

但是 TEntity 仅在运行时才知道。

我知道如何调用这样的方法:

Type classType = GetType();
MethodInfo mInfo = classType.GetMethod("DoSomething", BindingFlags.Public | BindingFlags.Instance);
MethodInfo genericMInfo = mInfo.MakeGenericMethod(GetMyType());
Object result = genericMInfo.Invoke(this, <WHAT GOES HERE>);

如您所见,我不知道将什么传递给函数。这就是与This Question 的不同之处,后者调用了无参数的方法。

有什么办法可以做到吗?

编辑:完整示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Example
{
    public class Program
    {
        static void Main(string[] args)
        {
            var example = new Example();

            // Type know at compiletime:
            example.DoSomething<MyClass>((obj) => obj.Number > 2, (obj) => obj.Number = 500);

            // Type not know until runtime. Might be MyClass, MyOtherClass or MyNextClass
            Type classType = example.GetType();
            MethodInfo mInfo = classType.GetMethod("DoSomething", BindingFlags.Public | BindingFlags.Instance);
            MethodInfo genericMInfo = mInfo.MakeGenericMethod(GetMyType());

            var result = genericMInfo.Invoke(example, new object[] { /* Expression<Func<TEntity, bool>> and Action<TEntity> */ });
            // My problem now is how to create this Expression and Action even if i know TEntity only at runtime
        }

        static Type GetMyType()
        {
            // Imagine some user-input or any other dark magic here. For the example i`ll just return a type
            return typeof(MyOtherClass);
        }
    }

    public class Example
    {
        public bool DoSomething<TEntity>(Expression<Func<TEntity, bool>> expression, Action<TEntity> action) where TEntity : MyClass
        {
            // Real code does something more useful, but its the same principle
            var myList = new List<TEntity>(GetItems<TEntity>());

            if (myList.Count > 0)
            {
                var entry = myList.AsQueryable().Where(expression).FirstOrDefault();

                if (entry != null)
                {
                    action(entry);

                    return true;
                }
            }

            return false;
        }

        private IEnumerable<T> GetItems<T>()
        {
            // Real code does something diffrent
            for (int i = 0; i < 5; i++)
            {
                yield return (T)Activator.CreateInstance(typeof(T), i);
            }
        }
    }

    public class MyClass
    {
        public MyClass(int number)
        {
            Number = number;
        }

        public int Number { get; set; }
    }

    public class MyOtherClass : MyClass
    {
        public MyOtherClass(int number)
            : base(number++)
        {
        }
    }

    public class MyNextClass : MyClass
    {
        public MyNextClass(int number)
            : base(number--)
        {
        }
    }
}

【问题讨论】:

  • 这个问题不清楚。你到底想用expressionTEntity 做什么?
  • @Liam 我编辑了这个问题。希望它现在清楚了。我不知何故需要构建一个只有在运行时才知道的确切类型的表达式和动作。
  • WHAT GOES HERE,你的参数。您如何将参数传递到您调用它的任何地方?这还不清楚。请为您的问题创建一个Minimal, Complete, and Verifiable example
  • @Liam 我附上了完整的例子。
  • 好吧,这是一个非常多的代码,只是为了替换 where 子句。反射也将是低效的。为什么你需要对看似简单的代码块进行这种级别的重用。我认为你做这个比它需要的要复杂得多,实际上是零收益。

标签: c# generics reflection anonymous-methods


【解决方案1】:

不幸的是it's not possible to define generic anonymous methods

最大的问题是:你的表情/动作来自哪里?

在我看来,可能的解决方案包括将您的操作声明为单独的方法,或使用辅助方法来创建您的操作/表达式。

// Create a helper method to create the expression
public static Expression<Func<TEntity, bool>> MakeExpression<TEntity>()
{
    // your custom expression here
    return x => true;
}

// Declare your action as a generic method
public static void MyAction<TEntity>(TEntity input)
{
    // your action here
}

// Then you can use it like this:
var func = typeof(Example).GetMethod("MakeExpression", BindingFlags.Public | BindingFlags.Instance).MakeGenericMethod(classType).Invoke(example, new object[0]);
var action = typeof(Example).GetMethod("MyAction", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(classType ).CreateDelegate(typeof(Action<>).MakeGenericType(classType));
genericMInfo.Invoke(example, new object[] { func, action });

您也可以对操作使用相同的“表达式工厂”方法:

public static Action<TEntity> MakeAction<TEntity>()
{
    // your custom action here.
    return e => { };
}

// Then:
var action = typeof(Example).GetMethod("MakeAction", BindingFlags.Public | BindingFlags.Instance).MakeGenericMethod(classType).Invoke(example, new object[0]);

另一种可能的解决方案包括使用Expression 类的building your expression/action dynamically

// Simple(!) example
var func = Expression.Lambda(Expression.Constant(true), Expression.Parameter(classType));

【讨论】:

  • @Max 很高兴我能帮上忙。出于好奇,告诉我你最终使用了什么。
最近更新 更多