eval(string)to C#code
-
05-07-2019 - |
質問
実行時に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#プログラムを作成し、 <で動的にコンパイルします< code> CSharpCodeProvider 。
- 式ツリーを構築し、コンパイルして実行します
- 評価を自分で実行します(実際には、オペレータなどによっては最も簡単な場合があります)
CodeDOMライブラリを使用するか、Expressionツリーを作成してコンパイルし、実行する必要があります。式ツリーを構築することが最良の選択肢だと思います。
もちろん、演算子に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; }
}
より良い設計は、ルールがテスト自体を(または任意の値に)適用することです
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 });
}
上記のコードはすべて概念実証にすぎず、健全性チェックに欠けており、深刻なパフォーマンスの問題があります。
さらに洗練されたい場合は、Reflection.EmitをDynamicMethodインスタンスで使用して(デフォルトの比較インスタンスではなく適切な演算子を使用して)実行できますが、これにはオーバーライドされた演算子を持つ型の複雑な処理が必要になります。
チェックコードを非常に汎用的にすることにより、将来必要に応じてより多くのテストを含めることができます。本質的に、tから関数のみを対象とするコードの部分を分離します-&gt;これらの機能を提供するコードのtrue / false。
免責事項:私はプロジェクトの所有者です Eval Expression.NET
このライブラリは、JS Evalに相当します。ほとんどすべての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
を使用できます10進数
これは最終的な設計ではありません。いくつかのアイデアを提供するだけです(たとえば、 Check()
メソッドなどを使用して、クラスが独自にデリゲートを評価する可能性があります)。
リフレクションによってフィールドを取得できます。そして、演算子をメソッドとして実装し、リフレクションまたはいくつかのタイプの列挙デリゲートマッピングを使用して演算子を呼び出します。演算子には、少なくとも2つのパラメーター、入力値、およびテストに使用する値が必要です。
コードを動的にコンパイルせずに(完全ではない)完全なC#コードをオンザフライで評価するエレガントな方法をおそらく見つけることはできないでしょうが、ほとんど確実に短い順序でルールを評価することができますDLR(IronPython、IronRubyなど)またはカスタム構文を解析して実行する式評価ライブラリを使用します。 C#と非常によく似た構文を提供するScript.NETが1つあります。
こちらをご覧ください:評価.NET(C#)でのランタイムの式
少しのPythonを学ぶ時間/傾向がある場合、IronPythonとDLRはすべての問題を解決します。 IronPythonでアプリを拡張する