동적으로 생성 된 람다를 객체 인스턴스에 적용

c# expression expression-trees lambda

문제

문자열에서 시작하는 람다를 동적으로 만드는 코드가 있습니다. 예를 들어, 다음과 같은 필터 클래스가 있습니다.

public class Criteria {
    public string Property { get; set; }
    public string Operator { get; set; }
    public string Value { get; set; }
}

그리고 x => x.Name == "Foo" 와 같은 Criteria 인스턴스에서 시작하는 람다를 만들 수 있습니다.

Criteria c = new Criteria() {
    Property = "Name",
    Operator = "equal",
    Value = "Foo"
}

클래스가 있다고 가정하면

public class Receipt {
    public string Name { get; set; }
    public int Amount { get; set; }
    [other props omitted]
    public ICollection<ReceiptDetail> Details { get; set; }
}

나는 다음과 같이하고 싶다.

  1. 모든 객체에 람다 적용 (람다는 Receipt 클래스의 ParameterExpression으로 만들어야한다는 것을 알고 있습니다)
  2. 람다의 부울 결과를 다시 얻는다 (예 : 이름이 Foo와 같은가?)
  3. Count () 메서드에 동일한 로직을 적용합니다 (예 : receipt.Details.Count ()에 대해 확인하는 람다 작성).

이것이 가능한가?

편집 : 의견에 따라, 나는 조금 더 내 요구를 정교하고 있습니다. 이 코드는 내가 가지고있는 요구 사항에 응답 할 수있는 기회를 제공 할 것입니다. 내 개체에 대해 지정된 규칙이 있으면 응용 프로그램이 조금 다르게 동작해야합니다. 이것이 일반적인 요구 사항이지만 더 많은 규칙이 추가되면서 코드를 확장 할 수있는 코드를 만들고 싶습니다. 사실 나는 5 가지 규칙 유형 만 있습니다.

  • 입력이 특정 요일에 오는지 확인하십시오.
  • 입력이 특정 시간 범위에 오는지 확인하십시오.
  • 입력의 필드 "X"가 값보다 작거나 같거나 큰지 확인
  • 입력의 필드 "Y"에 값이 포함되어 있는지 확인하십시오.
  • 콜렉션 인 입력 "Z"필드의 값이 값보다 작거나 같거나 큰지 확인하십시오.

첫 번째 4 점에 대해서는 P.Brian.Mackey 대답 과 같은 코드를 사용하여 동적으로 람다 식을 만들 수있었습니다.이 패턴을 사용하여 Specification 패턴을 사용하여 객체 자체에 적용 할 수있었습니다.

마지막 점은 거의 같은 방식으로 구현해야하지만 유일한 차이점은 식의 왼쪽 부분이 속성이 아니라 메서드 호출 (특히 ICollection<T>.Count() 메서드)이라는 점입니다.

수락 된 답변

여기에 당신을 시작하게하는 뭔가가 있습니다. 개선의 여지는 충분합니다. 특히 못생긴 공장. 이 데모는 모범 사례 또는 공장 패턴 데모가 아닌 표현을 사용하여 문제를 해결하는 방법을 보여줍니다. 불분명 한 점이 있으면 명확히 해 주시기 바랍니다.

용법

    [Test]
    public void ApplySameLogicToCollectionsCount()
    {
        var receipt = new Receipt();
        var details = new ReceiptDetail();
        var details2 = new ReceiptDetail();
        receipt.Details.Add(details);
        receipt.Details.Add(details2);
        var result = LambdaGeneratorFactory<ICollection<ReceiptDetail>>.Run(detailsCount);
        Assert.IsTrue(result(receipt.Details));
    }

공장

 public static class LambdaGeneratorFactory<T>
    {
        //This is an ugly implementation of a Factory pattern.
        //You should improve this possibly with interfaces, maybe abstract factory.  I'd start with an ICriteria.
        public static Predicate<T> Run(Criteria criteria)
        {
            if (typeof(T) == typeof (Receipt))
            {
                return CreateLambda(criteria);
            }
            else if (typeof (T) == typeof (ICollection<ReceiptDetail>))
            {
                return CreateLambdaWithCount(criteria);
            }

            return null;
        }
        private static Predicate<T> CreateLambda(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression left = Expression.Property(pe, typeof(T).GetProperty(criteria.Property));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }

        private static Predicate<T> CreateLambdaWithCount(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression count = Expression.Property(pe, typeof(T).GetProperty("Count"));
            Expression left = Expression.Call(count, typeof(Object).GetMethod("ToString"));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }
    }

기준

    private Criteria detailsCount = new Criteria()
    {
        Property = "Details",
        Operator = "equal",
        Value = "2"
    };

ICriteria 전환하면 ICriteria 것이 더 깨끗해집니다. 더 나은 공장이며 ToString 필요 없습니다. 인터페이스에 프로그램하십시오.

이 모든 내용은 약간 펑키 한 느낌을줍니다. 문자열에서 함수를 생성하는 요점은 무엇입니까? 문법에서 C #을 생성하는쪽으로 향하고 있다는 느낌을받습니다. 나는 그것이 잘 될 것이라고 확신하지 않습니다. 평범하지 않은 구현에서는 lex / yacc를 먼저 고려하십시오. 실용적인 프로그래머 "미니 언어 구현"에서이 작업에 대한 자세한 내용을 확인할 수 있습니다.


인기 답변

당신의 질문은 흥미로운 질문이며 요구 사항을 이해하고 싶습니다. 데모를 만들었는데 궁금합니다. 데모가 당신이 이루고자하는 것과 어떻게 다른가요? 여기 작업 버전이 있습니다 https://dotnetfiddle.net/AEBZ1w .

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        Criteria c = new Criteria() { 
            Property = "Name", 
            Operator = "==", 
            Value = "Foo" };

        var queryable = (new List<Receipt>() { 
            new Receipt { Name = "Foo", Amount = 1 },
            new Receipt { Name = "Foo", Amount = 2 }, 
            new Receipt { Name = "Bar" }  
        }).AsQueryable();

        var parameter = Expression.Parameter(typeof(Receipt), "x");
        var property = Expression.Property(parameter, typeof(Receipt).GetProperty(c.Property));
        var constant = Expression.Constant(c.Value);
        var operation = Expression.Equal(property, constant);
        var expression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { queryable.ElementType },
            queryable.Expression, 
            Expression.Lambda<Func<Receipt, bool>>(operation, new ParameterExpression[] { parameter })
        );

        Console.WriteLine("Linq Expression: {0} \n", expression.ToString());
        Console.WriteLine("Results: \n");

        var results = queryable.Provider.CreateQuery<Receipt>(expression);
        foreach(var r in results)
        {
            Console.WriteLine("{0}:{1}", r.Name, r.Amount);
        }
    }
}

public class Criteria
{
    public string Property, Operator, Value;
}

public class ReceiptDetail
{
    public string ItemName;
}

public class Receipt
{
    public string Name { get; set; }
    public int Amount;
    public ICollection<ReceiptDetail> Details;
}

참고 문헌



아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow
아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow