使用反射创建表达式>

本文关键字:func 表达式 反射 创建 | 更新日期: 2023-09-27 18:35:13

我使用最小起订量来创建数据集的模拟。

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

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

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

我的模拟有很多属性,所以我想使用反射来执行此设置步骤。我已经设法通过反射完成了该过程的Returns部分,但我被困在 lambda 方法来Setup.

Setup需要

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

我想动态地创建这个。使用反射,我有以下内容:

酒店名称:"学术周期"

类型IQueryable<AcademicCycle>

类型AcademicCycle

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

使用反射创建表达式<func<,>>

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

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<...>>将其作为参数传递给设置函数,因为您不知道Func的泛型类型参数。因此,您还必须通过反射来调用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<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; }
    }
}

此方法应该构造 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;
    }