【问题标题】:Create an Expression<Func<,>> using reflection使用反射创建一个 Expression<Func<,>>
【发布时间】:2012-05-29 13:48:30
【问题描述】:

我正在使用 Moq 创建数据集的模拟。

我创建了一个小助手类,它允许我拥有一个内存存储而不是一个使单元测试变得轻而易举的数据库。这样我就可以从我的模拟数据集中添加和删除项目,这允许我测试我的插入和删除服务调用。

在模拟设置期间,我有一行如下所示

this.Setup(i => i.AcademicCycles).Returns(mockStore.GetList<AcademicCycle>());

我的模拟有很多属性,所以我想使用反射来执行这个设置步骤。我已经设法通过反射工作到Returns 过程的一部分,但我被困在Setup 的lambda 方法上。

Setup 接受一个

Expression&lt;Func&lt;GoalsModelUnitOfWork, IQueryable&lt;AcademicCycle&gt;&gt;&gt; 对应于i =&gt; i.AcademicCycles

我想动态创建它。使用反射我有以下内容:

属性名称:“AcademicCycles”

类型IQueryable&lt;AcademicCycle&gt;

类型AcademicCycle

我在 lambda 语句中也有 i 的实例,它是 GoalsModelUnitOfWork

【问题讨论】:

    标签: c# reflection lambda moq expression


    【解决方案1】:

    动态创建表达式的代码如下:

    ParameterExpression parameter = Expression.Parameter(typeof (GoalsModelUnitOfWork), "i");
    MemberExpression property = Expression.Property(parameter, "AcademicCycles");
    
    var queryableType = typeof (IQueryable<>).MakeGenericType(typeof (AcademicCycle));
    var delegateType = typeof (Func<,>).MakeGenericType(typeof (GoalsModelUnitOfWork), queryableType);
    
    var yourExpression = Expression.Lambda(delegateType, property, parameter);
    

    结果将具有所需的类型,但问题是 Expression.Lambda() 的返回类型是 LambdaExpression 并且您不能执行类型转换为 Expression&lt;Func&lt;...&gt;&gt; 以将其作为参数传递给您的设置函数,因为你不知道Func 的泛型类型参数。所以你也必须通过反射来调用Setup方法:

    this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression);
    

    【讨论】:

    • 如果你静态知道参数和返回类型,实际上 Expression.Lambda 的结果可以转换为Expression&lt;Func&lt;...&gt;&gt;。在内部 Expression.Lambda 确实构造了一个适当的 Expression&lt;Func&lt;...&gt;&gt; 类型的实例,即使 Expression.Lambda 的返回类型是弱类型的。
    • 另外我认为你不需要中间两行。通过在更简单的情况下进行测试,var lambda = Expression.Lambda(parameter, property) 应该可以工作(Expression.Lambda 将从类型和属性中计算出委托类型)。但是我的测试代码与你的略有不同,并且使用了更简单的类型,所以你的里程可能会有所不同......!
    【解决方案2】:

    我决定试一试,最终得到了这段糟糕透顶的代码。

    我不是反思专家,这只是第一次尝试让某些东西发挥作用。我会对人们有哪些其他方法非常感兴趣,或者是否有任何 relfection 包装库可以使它变得更好。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Moq;
    using Xunit;
    
    namespace MyExample
    {
        public class Tests
        {
            [Fact]
            public void Test()
            {
                Dictionary<Type, object> data = new Dictionary<Type, object>
                {
                    { typeof(IQueryable<Cycle>), new List<Cycle> { new Cycle { Name = "Test" } }.AsQueryable() },
                    { typeof(IQueryable<Rider>), new List<Rider> { new Rider { Name = "1"}, new Rider { Name = "2" } }.AsQueryable() }
                };
    
                var mock = new Mock<IDataContext>();
                var setup = mock.GetType().GetMethods().Single(d => d.Name == "Setup" && d.ContainsGenericParameters);
                var param = Expression.Parameter(typeof(IDataContext), "i");
                foreach (var property in typeof(IDataContext).GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    // Build lambda
                    var ex = Expression.Lambda(Expression.MakeMemberAccess(param, property), param);
    
                    // Get generic version of the Setup method
                    var typedSetup = setup.MakeGenericMethod(property.PropertyType);
    
                    // Run the Setup method
                    var returnedSetup = typedSetup.Invoke(mock, new[] { ex });
    
                    // Get generic version of IReturns interface
                    var iReturns = typedSetup.ReturnType.GetInterfaces().Single(d => d.Name.StartsWith("IReturns`"));
    
                    // Get the generic Returns method
                    var returns = iReturns.GetMethod("Returns", new Type[] { property.PropertyType });
    
                    // Run the returns method passing in our data
                    returns.Invoke(returnedSetup, new[] { data[property.PropertyType] });
                }
    
                Assert.Equal(1, mock.Object.Cycles.Count());
            }
        }
    
        public class Cycle
        {
            public string Name { get; set; }
        }
    
        public class Rider
        {
            public string Name { get; set; }
        }
    
        public interface IDataContext
        {
            IQueryable<Cycle> Cycles { get; set; }
    
            IQueryable<Rider> Riders { get; set; }
        }
    }
    

    【讨论】:

      【解决方案3】:

      这个方法应该构造 lambda 表达式。由于您通过反射调用 Setup 方法,因此不需要强类型的 lambda 表达式;当您调用 Invoke 时,您将把它作为对象数组的一部分传递:

          public LambdaExpression PropertyGetLambda(string parameterName, Type parameterType, string propertyName, Type propertyType)
          {
              var parameter = Expression.Parameter(parameterType, parameterName);
              var memberExpression = Expression.Property(parameter, propertyName);
              var lambdaExpression = Expression.Lambda(memberExpression, parameter);
              return lambdaExpression;
          }
      

      我认为您实际上不需要参数名称。如果我是对的,您可以简化一下:

          public LambdaExpression PropertyGetLambda(Type parameterType, string propertyName, Type propertyType)
          {
              var parameter = Expression.Parameter(parameterType);
              var memberExpression = Expression.Property(parameter, propertyName);
              var lambdaExpression = Expression.Lambda(memberExpression, parameter);
              return lambdaExpression;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-11
        • 1970-01-01
        • 1970-01-01
        • 2022-12-07
        • 2021-07-29
        相关资源
        最近更新 更多