4

実行時にC#で以下を評価することは可能ですか?

Field3つのプロパティ( 、、)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)はより良い方法、またはよりサポートされた方法が必要です。

4

8 に答える 8

10

いいえ、C#はこのようなものを直接サポートしていません。

最も近いオプションは次のとおりです。

  • 完全に有効なC#プログラムを作成し、を使用して動的にコンパイルしCSharpCodeProviderます。
  • 式ツリーを構築し、コンパイルして実行します
  • 自分で評価を行います(オペレーターなどによっては、これが実際に最も簡単な場合があります)
于 2009-07-30T16:45:42.697 に答える
4

免責事項:私はプロジェクトEvalExpression.NETの所有者です

このライブラリは、JSEvalに相当するものに近いものです。ほとんどすべての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);
于 2016-02-09T22:28:43.433 に答える
2

より良い設計は、ルールがテスト自体(または任意の値)を適用することです。

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から分離します。

于 2009-07-30T17:27:02.767 に答える
2

あなたが何を言っているのか完全にはわかりません。少し明確にしてみてください。

文字列式を取得して、実行時にC#で評価しますか?もしそうなら、答えはノーです。C#は、このようなタイプの動的評価をサポートしていません。

于 2009-07-30T16:44:03.540 に答える
2

CodeDOMライブラリを使用するか、式ツリーを作成してコンパイルし、実行する必要があります。式ツリーを構築することが最良の選択肢だと思います。

もちろん、演算子にswitchステートメントを入れることもできますが、とにかく使用できる演算子の数は限られているため、悪くはありません。

式ツリー(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; }
}
于 2009-07-30T16:45:40.980 に答える
1

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()メソッドか何かを介してそれ自身でデリゲートを評価させるでしょう)。

于 2009-07-30T17:20:26.837 に答える
0

反射によってフィールドを取得できます。次に、演算子をメソッドとして実装し、リフレクションまたはいくつかのタイプの列挙型-委任マッピングを使用して演算子を呼び出します。演算子には、入力値とテストに使用する値の2つ以上のパラメーターが必要です。

于 2009-07-30T16:50:38.613 に答える
0

動的にコンパイルするコードを使用せずに完全なC#コードをその場で評価するエレガントな方法はおそらく見つからないことは事実ですが(これは決してきれいではありません)、どちらかを使用してルールを短い順序で評価することはほぼ確実です。 DLR(IronPython、IronRubyなど)またはカスタム構文を解析して実行する式エバリュエーターライブラリ。C#と非常によく似た構文を提供するScript.NETがあります。

ここを見てください:.NET(C#)での実行時の式の評価

少しPythonを学ぶ時間/傾向がある場合は、IronPythonとDLRがすべての問題を解決します 。IronPythonでアプリを拡張する

于 2009-07-30T18:19:58.777 に答える