【问题标题】:Get Func types from a List of Expression<Func>从 Expression<Func> 列表中获取 Func 类型
【发布时间】:2016-11-13 09:25:49
【问题描述】:

我的班级有以下列表:

public class Mapper {

  public List<Expression> Expressions { get; set; } = new List<Expression>();

}

我知道所有的表达式都是:

Expression<Func<InType, OutType>>

问题在于列表中的 InType 和 OutType 不同...

我试图定义类似的东西,但它没有编译。

Expression<Func<,>>

稍后我将需要遍历每个表达式并获取函数的输入和输出类型。

这可能吗?

【问题讨论】:

  • List&lt;Expression&gt;&gt; 是错字吗?
  • 如果 InType 和 OutType 不同,您可能需要重新考虑是否应将它们保留在同一个列表中。否则,InType 和 OutType 可能具有相同的基类或实现公共接口。
  • 为什么不将列表声明为List&lt;LambdaExpression&gt;?然后您还可以通过编程方式获取参数(即 InType)和返回的类型(即 OutType)
  • @bassfader LambdaExpression is 一个(继承自)表达式 = 共享 ParametersReturnType 属性。
  • @marsze 我知道Expression 是(所有表达式的)基本类型。但ParametersReturnType 属性由LambdaExpression 定义,不属于Expression 基类,它们由LambdaExpression 类定义(参见this link on MSDN

标签: c#


【解决方案1】:

不,这通常是不可能的。

Expression&lt;Func&lt;_,_&gt;&gt; 这样的类型被称为更高种类的类型。 C# 语言无法表达这种类型。

【讨论】:

【解决方案2】:

您可以使用以下代码

            List<Expression> list = new List<Expression>();

            var typePairs = list.OfType<LambdaExpression>().Select(x =>
                new
                {
                    inType = x.Parameters[0].Type,
                    outType = x.ReturnType
                });

【讨论】:

  • 使用 LambdaExpression 和 Expression 有什么区别?
  • @MiguelMoura 有很多表达式不是 lambda。
  • @MiguelMoura 类型表达式没有参数和 ReturnType。它来自 LambdaExpression
【解决方案3】:

我希望你愿意做一些反思。仅使用编译时类型是不可能的,但是通过反射,您可以在运行时提取类型。

缺点是:

  • 您必须确保列表只包含Expression&lt;Func&lt;In, Out&gt;&gt;s,而不包含其他类型的表达式。 (无法在编译时检查)
  • 很慢

示例代码:

class Program
{
    public static void ConsumeExpressions(List<LambdaExpression> exprs)
    {
        var consumerMethod = typeof(Program).GetMethod("ConsumeExpression", BindingFlags.Public | BindingFlags.Static);
        foreach (var expr in exprs)
        {
            var inType = expr.Parameters[0].Type;
            var outType = expr.ReturnType;
            var genericMethod = consumerMethod.MakeGenericMethod(inType, outType);
            genericMethod.Invoke(null, new object[] { expr });
        }
    }

    public static void ConsumeExpression<TInType, TOutType>(Expression<Func<TInType, TOutType>> expr)
    {
        Console.WriteLine("in: {0}, out: {1}, {2}", typeof(TInType).Name, typeof(TOutType).Name, expr);
    }

    static void Main(string[] args)
    {
        ConsumeExpressions(new List<LambdaExpression>
        {
            (Expression<Func<int, string>>)(i => ""),
            (Expression<Func<string, int>>)(s => 0)
        });
    }
}

编辑

根据 Brahim 的帖子建议使用LambdaExpressions,这样反射部分会更短更清晰。

【讨论】:

    【解决方案4】:

    这假设只有一个参数:

    public List<LambdaExpression> Expressions { get; set; } = new List<LambdaExpression>();
    
    foreach (LambdaExpression expression in Expressions)
    {
        var inType = expression.Paramters[0].Type;
        var outType = expresssion.ReturnType;
    }
    

    根据用例,最好让 in 和 out 类型有一个公共基类或共享一个接口,这样就不需要检查它们的类型。

    编辑:

    只有 LambdaExpressions 具有 ReturnTypeParameters 属性。 Expression&lt;TDelegate&gt; 继承自 LambdaExpression

    【讨论】:

      【解决方案5】:

      所有类型都派生自 object...您可以轻松使用 object,但会失去您想要的类型安全性。

      也许定义一个接口 IParameter 来定义逻辑的语义,例如InType、OutType、HasInputValue、HasOutputValue。

      使用接口实现解决问题。

      也许定义一个 Number 或 NumberResult 类来实现该接口并将其或该接口用作列表中公开的类型。

      【讨论】:

        【解决方案6】:

        这里有一个可以帮助你的完整示例

        class Program
        {
            static void Main(string[] args)
            {
                List<Expression>  expressions =  new List<Expression>();
                Expression <Func<string,int>> func = a => a.Length;
        
                expressions.Add(func);
                foreach (var expression in expressions)
                {
                    LambdaExpression lmdExpression = expression  as LambdaExpression;
                    if (lmdExpression!=null)
                    {    
                        //get  all params 
                         List<string>   paramsList  = new List<string>();
                        foreach (var parameterExpression in lmdExpression.Parameters)
                        {                        paramsList.Add(parameterExpression.Type.ToString());  
                        }
                        var returnedType = lmdExpression.ReturnType.ToString(); 
                        //and  here you can use a big switch  to  invoke your needed expression                 
                    }
                }        
                Console.ReadLine();     
            }
        }
        

        为什么要使用 Expression 和 LambdaExpression?

        因为LambdaExpression 是派生自Expression&lt;TDelegate&gt; 的抽象类,并且由于您不知道您使用的是哪种类型的Func&lt;T,T&gt;,所以您必须使用基类

        【讨论】:

        • 为什么要使用 Expression 和 LambdaExpression?
        【解决方案7】:

        更新:感谢所有同事和他们的 cmets。替换了我原来的答案。

        首先,您绝对不需要Expression 来完成这项工作,因为最重要的是,它是一个带有扭曲的类型定位器。看看下面的实现:

        public class FuncMapper
        {
            Dictionary<Type, Delegate> _funcs = new Dictionary<Type, Delegate>();
        
            public void Register<TIn, TOut>(Func<TIn, TOut> func)
            {
                _funcs.Add(func.GetType(), func);
            }
        
            public TOut Execute<TIn, TOut>(TIn param)
            {
                return ((Func<TIn, TOut>)_funcs[typeof(Func<TIn, TOut>)])(param);
            }
        }
        

        那么使用就这么简单:

        class Program
        {
            static void Main(string[] args)
            {
                FuncMapper funcMapper = new FuncMapper();
        
                funcMapper.Register<string, string>(DoString);
                funcMapper.Register<int, int>(DoInt);
        
                Console.WriteLine("Value: {0}", funcMapper.Execute<string, string>("Test"));
                Console.WriteLine("Value: {0}", funcMapper.Execute<int, int>(10));
        
                Console.Read();
            }
        
            static string DoString(string param)
            {
                return param;
            }
        
            static int DoInt(int param)
            {
                return param;
            }
        }
        

        更新2:如果你真的需要表达,你可以通过以下方式更改Register方法:

        public void Register<TIn, TOut>(Expression<Func<TIn, TOut>> expression)
        {
            Func<TIn, TOut> func = expression.Compile();
        
            _funcs.Add(func.GetType(), func);
        }
        

        然后这样称呼它:

        funcMapper.Register<string, string>((param) => DoString(param));
        funcMapper.Register<int, int>((param) => DoInt(param));
        

        【讨论】:

        • 这不是一个可行的方法,因为 func 最多可以有 15 个参数
        • 从问题来看,似乎只使用了一个参数,如果不是,也许问题应该反映这一点。
        • Expression 不是协变的,因此您不能将Expression&lt;Func&lt;string, string&gt;&gt; 视为Expression&lt;Func&lt;object, object&gt;&gt;
        • @Servy Expression&lt;Func&lt;TIn, Tout&gt;&gt; 在逻辑上是 - 但在技术上不是,因为只有接口才能声明方差 - 在 TIn 中是协变的,在 TOut 中是逆变的。您可以相当简单地为它们编写方差转换器 - 尽管这个答案没有这样做。
        • @Martijn 但是参数在逻辑上甚至不是协变的,它们是逆变的,但是它用于协变庄园,所以即使这样也不能解决问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多