Как создать лямбда-выражения для каждого свойства класса - PullRequest
0 голосов
/ 29 октября 2018

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

var b = builder<MyTypeWith1000Properties>
    .WithProperty(x=>x.Property1)
    .WithProperty(x=>x.Property2)
    ...
    .WithProperty(x=>x.Property1000);

Код повторяется во многих местах для разных типов, а не только для MyTypeWith1000Properties. Я думал о создании какого-то расширения, например:

var b = builder<MyTypeWith1000Properties>
    .WithAllProperties();

, а затем в WithAllProperties я мог бы перебирать свойства типа, используя Reflection, например:

public static IDataExtractor<T> WithAllProperties(this IDataExtractor<T> extractor)
{
    var properties = typeof(T).GetProperties();
    foreach (var property in properties)
    {
        extractor = extractor.WithProperty(/*the problem is here/*);
    }
    return extractor;
}

Как преобразовать переменную свойства в цикле в соответствующее выражение

Expression<Func<TRow, TValue>> propertyExpression

как это то, что ожидает WithProperty

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

WithProperty - это обобщенный метод, который принимает параметр типа, подразумеваемый типом результата выражения доступа к члену свойства, который TValue представляет в своем объявлении. Поскольку вы хотите использовать отражение для генерации лямбда-выражений, вы должны также динамически выполнять вызов WithProperty, чтобы вы могли вызывать вызов с правильным типом (например, WithProperty<String> для свойства String).

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

public static class IDataExtractorExt {
    public static IDataExtractor<TRow> WithAllProperties<TRow>(this IDataExtractor<TRow> extractor) {
        var p = Expression.Parameter(typeof(IDataExtractor<TRow>), "p"); // final lambda parameter
        Expression ansBody = p; // start with p => p

        var withPropGenericMI = typeof(IDataExtractor<TRow>).GetMethod("WithProperty"); // lookup WithProperty<> generic method
        var properties = typeof(TRow).GetProperties();
        foreach (var property in properties) {
            var withPropMI = withPropGenericMI.MakeGenericMethod(property.PropertyType); // instantiate generic WithProperty<> to property type
            var pxp = Expression.Parameter(typeof(TRow), "x"); // property accessor lambda parameter
            var pxb = Expression.PropertyOrField(pxp, property.Name); // property accessor expression x.property
            Expression propExpr = Expression.Lambda(pxb, pxp); // x => x.property
            ansBody = Expression.Call(ansBody, withPropMI, propExpr); // build up p => p.WithProperty(x => x.property)...
        }

        return ((IDataExtractor<TRow>)(Expression.Lambda(ansBody, p).Compile().DynamicInvoke(extractor)));
    }
}
0 голосов
/ 29 октября 2018

Обновление - установка правильного значения параметра в Expression.Lambda>

Вы можете попробовать что-то вроде этого

public static class BuilderExtension
{
    public static IDataExtractor<T> WithAllProperties<T>(this IDataExtractor<T> extractor)
    {
        var properties = typeof(T).GetProperties();
        foreach (var propertyInfo in properties)
        {
            var parameter = Expression.Parameter(typeof(T), "x");
            var property = Expression.Property(parameter, propertyInfo);
            var lambda = Expression.Lambda<Func<T, object>>(property,parameter);
            extractor = extractor.WithProperty(lambda);
        }
        return extractor;
    }
}

Предположим, у вас есть следующие структуры классов

public class MyTypeWith100Properties
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }
    public string Property4 { get; set; }
}


public interface IDataExtractor<T>
{
    IDataExtractor<T> WithProperty(Expression<Func<T, object>> expr);
}

public class DataExtractor<T> : IDataExtractor<T>
{
    public List<Expression<Func<T, object>>> Expressions { get; private set; }

    public DataExtractor() {
        Expressions = new List<Expression<Func<T, object>>>();
    }
    public IDataExtractor<T> WithProperty(Expression<Func<T, object>> expr)
    {
        Expressions.Add(expr);
        return this;
    }
}

Тогда, если вы запустите это

var builder = new DataExtractor<MyTypeWith100Properties>();
var b = builder.WithAllProperties<MyTypeWith100Properties>() 
                         as DataExtractor<MyTypeWith100Properties>;

var instance = new MyTypeWith100Properties() {
                        Property1 = "This is property 1",
                        Property2 = "This is property 2",
                        Property3 = "This is property 3",
                        Property4 = "This is property 4"
                    };

foreach (var current in b.Expressions)
{
    var compiled = current.Compile();
    var result = compiled.Invoke(instance);
    Console.WriteLine(result);
}

Ваш вывод будет

Это свойство 1

Это собственность 2

Это собственность 3

Это собственность 4

...