使用reflection创建表达式<Func >

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

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

在模拟的设置过程中,我有一行如下所示

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

我的模拟有很多属性,所以我想使用reflection执行此设置步骤。 我已经设法通过reflection工作的Returns部分,但我坚持使用lambda方法来Setup

Setup需要一个

Expression<Func<GoalsModelUnitOfWork, IQueryable>>对应于i => i.AcademicCycles

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

物业名称:“AcademicCycles”

类型IQueryable

类型AcademicCycle

我还在lambda语句中有一个i的实例,它是一个GoalsModelUnitOfWork

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

 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>执行类型转换以将其作为参数传递给您设置函数,因为您不知道Func的generics类型参数。 所以你必须通过reflection来调用Setup方法:

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

我决定对它进行一次破解,并最终得到了这个可怕的代码。

我不是反思专家,这只是让事情变得有效的第一次尝试。 我会对人们有什么其他方法感兴趣,或者是否有任何一个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 data = new Dictionary { { typeof(IQueryable), new List { new Cycle { Name = "Test" } }.AsQueryable() }, { typeof(IQueryable), new List { new Rider { Name = "1"}, new Rider { Name = "2" } }.AsQueryable() } }; var mock = new Mock(); 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 Cycles { get; set; } IQueryable Riders { get; set; } } } 

该方法应该构造lambda表达式。 由于您通过reflection调用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; }