Вопрос по c# – Создайте выражение <Func <, >> используя отражение

27

Я использую Moq для создания макетов набора данных.

Я создал небольшой вспомогательный класс, который позволяет мне хранить в памяти вместо базы данных, что делает модульное тестирование быстрым. Таким образом, я могу добавлять и удалять элементы из моего фиктивного набора данных, это позволяет мне проверять мои вставки и удалять вызовы службы.

Во время настройки макета у меня есть строка, которая выглядит следующим образом

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

У моего макета много свойств, поэтому я хотел бы выполнить этот шаг настройки, используя отражение. Я сумелReturns часть процесса, работающего через отражение, но я застрял на лямбда-методSetup.

Setup занимает

Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>> что соответствуетi => i.AcademicCycles

и я хотел бы создать это динамически. Используя отражение у меня есть следующее:

Название объекта: & quot; AcademicCycles & quot;

ТипIQueryable<AcademicCycle>

ТипAcademicCycle

У меня также есть экземплярi в лямбда-выражении, которое являетсяGoalsModelUnitOfWork

Ваш Ответ

3   ответа
2

етод Setup с помощью отражения, вам не нужно строго типизированное лямбда-выражение; вы собираетесь передать его как часть массива объектов при вызове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;
    }
29

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);
На самом деле результат Expression.Lambda может быть приведен кExpression<Func<...>> если вы статически знаете параметры и возвращаемые типы. Внутренне Expression.Lambda создает экземпляр соответствующегоExpression<Func<...>> тип, хотя возвращаемый тип Expression.Lambda имеет слабый тип.
Также я не думаю, что вам нужны две средние строки. От тестирования в более простом случае,var lambda = Expression.Lambda(parameter, property) должен работать (Expression.Lambda будет определять тип делегата из типов и свойств). Однако мой тестовый код немного отличался от вашего и использовал более простые типы, поэтому ваш пробег может отличаться ...!
2

Я не эксперт по рефлексии, и это только первая попытка заставить что-то работать. Мне было бы очень интересно узнать, какие у людей есть другие подходы, и может ли какая-либо из библиотек-оболочек для рффектов сделать это лучше.

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; }
    }
}

Похожие вопросы