문제

런타임 시 C#에서 다음을 평가할 수 있습니까?

3개의 속성을 포함하는 클래스가 있습니다(Field,Operator,Value)

 rule.Field;
 rule.Operator;
 rule.Value;

이건 내 규칙 클래스야...

이제 루프가 생겼습니다.

foreach(item in items)
   {
       // here I want to create a dynamic expression to evaluate at runtime
       // something like
       if (item.[rule.field] [rule.operator] [rule.value])
           { do work }
   }

구문을 모르거나 C#에서 가능하다면 JS에서는 가능하다는 것을 알고 있지만 컴파일된 언어는 아닙니다.

업데이트

본질적으로 나는 방법을 원합니다 eval(stringCode) 또는 더 나은 지원 방법.

도움이 되었습니까?

해결책

나는 당신이 무슨 말을하는지 완전히 확신하지 못합니다. 조금 명확히해볼 수 있습니까?

문자열 표현식을 취하고 C#에서 런타임에 평가하고 싶습니까? 그렇다면 대답은 아니오입니다. C#은 이러한 유형의 동적 평가를 지원하지 않습니다.

다른 팁

아니요, C#은 이와 같은 것을 직접 지원하지 않습니다.

가장 가까운 옵션은 다음과 같습니다.

  • 전체 유효한 C# 프로그램을 만들고 동적으로 컴파일합니다. CSharpCodeProvider.
  • 빌드 an 발현 트리, 컴파일하고 실행하십시오
  • 직접 평가를 수행하십시오 (실제로 운영자 등에 따라 실제로 가장 쉬울 수 있음).

Codedom 라이브러리를 사용하거나 표현 트리를 만들고 컴파일하고 실행해야합니다. 표현 트리를 만드는 것이 최선의 선택이라고 생각합니다.

물론 운영자에 스위치 명령문을 넣을 수 있습니다. 어쨌든 사용할 수있는 운영자가 제한되어 있기 때문에 나쁘지 않습니다.

다음은 표현 나무 (LinqPad로 작성) 로이 작업을 수행하는 방법입니다.

void Main()
{   
    var programmers = new List<Programmer>{ 
        new Programmer { Name = "Turing", Number = Math.E}, 
        new Programmer { Name = "Babbage", Number = Math.PI}, 
        new Programmer { Name = "Lovelace", Number = Math.E}};


    var rule0 = new Rule<string>() { Field = "Name", Operator = BinaryExpression.Equal, Value = "Turing" };
    var rule1 = new Rule<double>() { Field = "Number", Operator = BinaryExpression.GreaterThan,  Value = 2.719 };

    var matched0 = RunRule<Programmer, string>(programmers, rule0);
    matched0.Dump();

    var matched1 = RunRule<Programmer, double>(programmers, rule1);
    matched1.Dump();

    var matchedBoth = matched0.Intersect(matched1);
    matchedBoth.Dump();

    var matchedEither = matched0.Union(matched1);
    matchedEither.Dump();
}

public IEnumerable<T> RunRule<T, V>(IEnumerable<T> foos, Rule<V> rule) {

        var fieldParam = Expression.Parameter(typeof(T), "f");
        var fieldProp = Expression.Property (fieldParam, rule.Field);
        var valueParam = Expression.Parameter(typeof(V), "v");

        BinaryExpression binaryExpr = rule.Operator(fieldProp, valueParam);

        var lambda = Expression.Lambda<Func<T, V, bool>>(binaryExpr, fieldParam, valueParam);
        var func = lambda.Compile();

        foreach(var foo in foos) {
            var result = func(foo, rule.Value);
            if(result)
                yield return foo;
        }

}

public class Rule<T> {
    public string Field { get; set; }
    public Func<Expression, Expression, BinaryExpression> Operator { get; set; }
    public T Value { get; set; }
}

public class Programmer {
    public string Name { get; set; }
    public double Number { get; set; }
}

더 나은 설계는 규칙이 테스트 자체(또는 임의의 값)에 적용되도록 하는 것입니다.

Func 인스턴스로 이 작업을 수행하면 다음과 같이 유연성이 극대화됩니다.

IEnumerable<Func<T,bool> tests; // defined somehow at runtime
foreach (var item in items)
{
    foreach (var test in tests)
    {
       if (test(item))
       { 
           //do work with item 
       }
    }
}

그러면 컴파일 타임에 강력한 유형 검사를 위한 특정 테스트는 다음과 같습니다.

public Func<T,bool> FooEqualsX<T,V>(V x)
{
    return t => EqualityComparer<V>.Default.Equals(t.Foo, x);
}

반사 형태의 경우

public Func<T,bool> MakeTest<T,V>(string name, string op, V value)
{
    Func<T,V> getter;
    var f = typeof(T).GetField(name);
    if (f != null)      
    {
        if (!typeof(V).IsAssignableFrom(f.FieldType))
            throw new ArgumentException(name +" incompatible with "+ typeof(V));
        getter= x => (V)f.GetValue(x);
    }
    else 
    {
        var p = typeof(T).GetProperty(name);
        if (p == null)      
            throw new ArgumentException("No "+ name +" on "+ typeof(T));
        if (!typeof(V).IsAssignableFrom(p.PropertyType))
            throw new ArgumentException(name +" incompatible with "+ typeof(V));
        getter= x => (V)p.GetValue(x, null);
    }
    switch (op)
    {
        case "==":
            return t => EqualityComparer<V>.Default.Equals(getter(t), value);
        case "!=":
            return t => !EqualityComparer<V>.Default.Equals(getter(t), value);
        case ">":
            return t => Comparer<V>.Default.Compare(getter(t), value) > 0;
        // fill in the banks as you need to
        default:
            throw new ArgumentException("unrecognised operator '"+ op +"'");
    }
}   

당신이되고 싶었다면 정말 컴파일 타임에 CSharpCodeProvider를 사용하여 다음과 같이 가정하여 함수를 컴파일할 수 있다는 사실을 모르고 모든 리터럴을 내성적으로 처리하고 처리합니다.

 public static bool Check(T t)
 {
     // your code inserted here
 }

물론 이것은 엄청난 보안 허점이므로 이에 대한 코드를 제공할 수 있는 사람은 누구이든 완전히 신뢰할 수 있어야 합니다.다음은 귀하의 특정 요구 사항에 대한 다소 제한된 구현입니다(전혀 온전성 검사가 없음).

private Func<T,bool> Make<T>(string name, string op, string value)
{

    var foo = new Microsoft.CSharp.CSharpCodeProvider()
        .CompileAssemblyFromSource(
            new CompilerParameters(), 
            new[] { "public class Foo { public static bool Eval("+ 
                typeof(T).FullName +" t) { return t."+ 
                name +" "+ op +" "+ value 
                +"; } }" }).CompiledAssembly.GetType("Foo");
    return t => (bool)foo.InvokeMember("Eval",
        BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod ,
        null, null, new object[] { t });
}

// use like so:
var f =  Make<string>("Length", ">", "2");

임의의 유형과 함께 작동하려면 컴파일러 매개변수에서 해당 유형을 참조하는 대상 어셈블리를 찾기 위해 좀 더 숙고해야 합니다.

private bool Eval(object item, string name, string op, string value)
{

    var foo = new Microsoft.CSharp.CSharpCodeProvider()
        .CompileAssemblyFromSource(
            new CompilerParameters(), 
            new[] { "public class Foo { public static bool Eval("+ 
                item.GetType().FullName +" t) "+
               "{ return t."+ name +" "+ op +" "+ value +"; } }"   
            }).CompiledAssembly.GetType("Foo");
    return (bool)foo.InvokeMember("Eval",
        BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod ,
        null, null, new object[] { item });
}

위의 모든 코드는 단순히 개념 증명일 뿐이며 온전성 검사가 부족하고 심각한 성능 문제가 있습니다.

더 멋지게 만들고 싶다면 DynamicMethod 인스턴스와 함께 Reflection.Emit를 사용할 수 있습니다(기본 비교자 인스턴스가 아닌 적절한 연산자 사용). 그러나 이렇게 하려면 재정의된 연산자가 있는 유형에 대해 복잡한 처리가 필요합니다.

검사 코드를 매우 일반적으로 만들면 나중에 필요에 따라 더 많은 테스트를 포함할 수 있습니다.본질적으로 t -> true/false의 함수에만 관심을 갖는 코드 부분을 이러한 함수를 제공하는 코드에서 분리합니다.

부인 성명: 저는 프로젝트의 소유자입니다 Evally Expression.net

이 라이브러리는 JS 평가에 가깝습니다. 모든 C# 언어를 거의 평가하고 컴파일 할 수 있습니다.

다음은 귀하의 질문을 사용하는 간단한 예입니다. 그러나 라이브러리는이 간단한 시나리오를 넘어선 것입니다.

int field = 2;
int value = 1;
string binaryOperator = ">";

string formula = "x " + binaryOperator + " y";

// For single evaluation
var value1 = Eval.Execute<bool>(formula, new { x = field, y = value });

// For many evaluation
var compiled = Eval.Compile<Func<int, int, bool>>(formula, "x", "y");
var value2 = compiled(field, value);

CSharpCodeProvider; switch 올바른 "연산자"를 선택하는 진술; DLR ... 그들은 당신이 할 수있는 모든 방법입니다. 그러나 그들은 나에게 이상한 해결책을 보인다.

대표를 사용하는 것은 어떻습니까?

당신의 가정 Field 그리고 Value 숫자는 다음과 같이 선언합니다.

delegate bool MyOperationDelegate(decimal left, decimal right);
...
class Rule {
    decimal Field;
    decimal Value;
    MyOperationDelegate Operator;
}

이제 '규칙'을 예를 들어 람다의 무리로 정의 할 수 있습니다.

Rule rule1 = new Rule;
rule1.Operation = (decimal l, decimal r) => { return l > r; };
rule1.Field = ... 

규칙을 만들고 원하는 방식으로 적용 할 수 있습니다.

IEnumerable<Rule> items = ...;

foreach(item in items)
{
    if (item.Operator(item.Field, item.Value))
    { /* do work */ }
}

만약에 Field 그리고 Values 숫자가 아니거나 유형은 특정 규칙에 따라 다르므로 사용할 수 있습니다. object 대신에 decimal, 약간의 캐스팅으로 모든 것이 작동하게 할 수 있습니다.

그것은 최종 디자인이 아닙니다. 그것은 당신에게 몇 가지 아이디어를주는 것입니다 (예 : 수업이 대의원을 자체적으로 평가할 것입니다. Check() 방법 또는 무언가).

반사로 필드를 검색 할 수 있습니다. 그런 다음 연산자를 메소드로 구현하고 반사 또는 일부 유형의 열거 방지 매핑을 사용하여 연산자를 호출하십시오. 연산자는 최소 2 개의 매개 변수, 입력 값 및 테스트에 사용하는 값이 있어야합니다.

동적 컴파일 코드 (결코 예쁘지 않음)를 사용하지 않고는 전체 C# 코드를 즉시 평가하는 우아한 방법을 찾지 못할 것이라는 것은 사실이지만, 거의 확실하게 규칙을 짧은 순서로 사용하여 둘 중 하나를 사용하여 거의 확실하게 평가할 수 있습니다. DLR (Ironpython, Ironruby 등) 또는 사용자 정의 구문을 구문 분석하고 실행하는 Expression Evaluator 라이브러리. C#과 매우 유사한 구문을 제공하는 Script.net이 있습니다.

여기를 참조하십시오.표현식 .net (c#)의 런타임 평가

작은 파이썬을 배우는 시간 / 성향이 있다면 Ironpython과 DLR은 모든 문제를 해결합니다.Ironpython으로 앱을 확장합니다

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top