簡體   English   中英

創建表達式 <Func<,> &gt;使用反射

[英]Create an Expression<Func<,>> using reflection

我使用Moq來創建數據集的模擬。

我創建了一個小助手類,它允許我有一個內存存儲而不是數據庫,使單元測試變得輕而易舉。 這樣我可以添加和刪除我的模擬數據集中的項目,這允許我測試我的插入和刪除服務調用。

在模擬的設置過程中,我有一行如下所示

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

我的模擬有很多屬性,所以我想使用反射執行此設置步驟。 我已經設法通過反射工作的Returns部分,但我堅持使用lambda方法進行Setup

Setup需要一個

Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>>對應於i => i.AcademicCycles

我想動態創建它。 使用反射我有以下內容:

物業名稱:“AcademicCycles”

類型IQueryable<AcademicCycle>

類型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<...>>執行類型轉換以將其作為參數傳遞給您設置函數,因為您不知道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;
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM