C# コードを動的に評価するにはどうすればよいですか?
-
08-06-2019 - |
質問
私はできる eval("something()");
JavaScript でコードを動的に実行します。C# で同じことを行う方法はありますか?
私がやろうとしていることの例は次のとおりです。整数変数があります(たとえば、 i
)そして私は名前で複数のプロパティを持っています:「プロパティ 1」、「プロパティ 2」、「プロパティ 3」など。ここで、「プロパティ」に対していくつかの操作を実行したいと思います。私 " の値に応じたプロパティ i
.
これは Javascript を使用すると非常に簡単です。C#でこれを行う方法はありますか?
解決
残念ながら、C# はそのような動的言語ではありません。
ただし、できることは、クラスとすべてが含まれた C# ソース コード ファイルを作成し、それを C# 用の CodeDom プロバイダーを通じて実行し、アセンブリにコンパイルして実行することです。
MSDN のこのフォーラムの投稿には、ページの下の方にいくつかのサンプルコードを含む回答が含まれています。
文字列から匿名メソッドを作成しますか?
これが非常に良い解決策であるとは言い難いですが、とにかく可能です。
その文字列にはどのようなコードが含まれると予想されますか?それが有効なコードのマイナーなサブセット (たとえば、単なる数式) である場合は、他の代替手段が存在する可能性があります。
編集:そうですね、最初に質問を徹底的に読むことが大切です。はい、ここで少し考えてみると役立つでしょう。
文字列を ; で分割すると、まず、個々のプロパティを取得するには、次のコードを使用してクラスの特定のプロパティの PropertyInfo オブジェクトを取得し、そのオブジェクトを使用して特定のオブジェクトを操作します。
String propName = "Text";
PropertyInfo pi = someObject.GetType().GetProperty(propName);
pi.SetValue(someObject, "New Value", new Object[0]);
他のヒント
Roslyn スクリプト API の使用 (詳細 サンプルはこちら):
// add NuGet package 'Microsoft.CodeAnalysis.Scripting'
using Microsoft.CodeAnalysis.CSharp.Scripting;
await CSharpScript.EvaluateAsync("System.Math.Pow(2, 4)") // returns 16
任意のコードを実行することもできます。
var script = await CSharpScript.RunAsync(@"
class MyClass
{
public void Print() => System.Console.WriteLine(1);
}")
そして、以前の実行で生成されたコードを参照します。
await script.ContinueWithAsync("new MyClass().Print();");
あまり。リフレクションを使用して目的を達成することはできますが、JavaScript ほど単純ではありません。たとえば、オブジェクトのプライベート フィールドを何かに設定したい場合は、次の関数を使用できます。
protected static void SetField(object o, string fieldName, object value)
{
FieldInfo field = o.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(o, value);
}
これは C# の eval 関数です。これを使用して、文字列から匿名関数 (ラムダ式) を変換しました。ソース: http://www.codeproject.com/KB/cs/evalcscode.aspx
public static object Eval(string sCSCode) {
CSharpCodeProvider c = new CSharpCodeProvider();
ICodeCompiler icc = c.CreateCompiler();
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("system.dll");
cp.ReferencedAssemblies.Add("system.xml.dll");
cp.ReferencedAssemblies.Add("system.data.dll");
cp.ReferencedAssemblies.Add("system.windows.forms.dll");
cp.ReferencedAssemblies.Add("system.drawing.dll");
cp.CompilerOptions = "/t:library";
cp.GenerateInMemory = true;
StringBuilder sb = new StringBuilder("");
sb.Append("using System;\n" );
sb.Append("using System.Xml;\n");
sb.Append("using System.Data;\n");
sb.Append("using System.Data.SqlClient;\n");
sb.Append("using System.Windows.Forms;\n");
sb.Append("using System.Drawing;\n");
sb.Append("namespace CSCodeEvaler{ \n");
sb.Append("public class CSCodeEvaler{ \n");
sb.Append("public object EvalCode(){\n");
sb.Append("return "+sCSCode+"; \n");
sb.Append("} \n");
sb.Append("} \n");
sb.Append("}\n");
CompilerResults cr = icc.CompileAssemblyFromSource(cp, sb.ToString());
if( cr.Errors.Count > 0 ){
MessageBox.Show("ERROR: " + cr.Errors[0].ErrorText,
"Error evaluating cs code", MessageBoxButtons.OK,
MessageBoxIcon.Error );
return null;
}
System.Reflection.Assembly a = cr.CompiledAssembly;
object o = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");
Type t = o.GetType();
MethodInfo mi = t.GetMethod("EvalCode");
object s = mi.Invoke(o, null);
return s;
}
オープンソースプロジェクトを書いたのですが、 ダイナミックエクスプレッソ, 、C# 構文を使用して記述されたテキスト式をデリゲート (または式ツリー) に変換できます。式は解析され、次のように変換されます。 式ツリー コンパイルやリフレクションを使用せずに。
次のように書くことができます:
var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");
または
var interpreter = new Interpreter()
.SetVariable("service", new ServiceExample());
string expression = "x > 4 ? service.SomeMethod() : service.AnotherMethod()";
Lambda parsedExpression = interpreter.Parse(expression,
new Parameter("x", typeof(int)));
parsedExpression.Invoke(5);
私の作品はScott Guの記事に基づいています http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx .
それらはすべて間違いなくうまくいきます。個人的には、その特定の問題については、おそらく少し異なるアプローチを取ると思います。おそらく次のようなものでしょう:
class MyClass {
public Point point1, point2, point3;
private Point[] points;
public MyClass() {
//...
this.points = new Point[] {point1, point2, point3};
}
public void DoSomethingWith(int i) {
Point target = this.points[i+1];
// do stuff to target
}
}
このようなパターンを使用する場合は、データが値ではなく参照によって保存されることに注意する必要があります。言い換えれば、プリミティブではこれを行わないでください。大きく肥大化したクラスの対応物を使用する必要があります。
それがまさに質問ではないことに気づきましたが、質問にはかなりよく答えられているので、おそらく別のアプローチが役立つかもしれないと思いました。
リフレクションを使用してプロパティを取得し、呼び出すことができます。このようなもの:
object result = theObject.GetType().GetProperty("Property" + i).GetValue(theObject, null);
つまり、プロパティを持つオブジェクトが「theObject」と呼ばれると仮定します:)
ウェブブラウザを実装してから、JavaScript を含む HTML ファイルをロードすることもできます。
それからあなたは行きます document.InvokeScript
このブラウザでの方法です。eval 関数の戻り値をキャッチして、必要なものすべてに変換できます。
いくつかのプロジェクトでこれを実行しましたが、完璧に機能しました。
それが役に立てば幸い
プロトタイプ関数を使用してそれを行うことができます。
void something(int i, string P1) {
something(i, P1, String.Empty);
}
void something(int i, string P1, string P2) {
something(i, P1, P2, String.Empty);
}
void something(int i, string P1, string P2, string P3) {
something(i, P1, P2, P3, String.Empty);
}
等々...
リフレクションを使用して、実行時にオブジェクトに対するデータバインディング式を解析および評価します。
パッケージを書きましたが、 シャープバイト.ダイナミック, 、コードを動的にコンパイルおよび実行するタスクを簡素化します。コードは、さらに詳しく説明するように、拡張メソッドを使用して任意のコンテキスト オブジェクトで呼び出すことができます。 ここ.
例えば、
someObject.Evaluate<int>("6 / {{{0}}}", 3))
3 を返します。
someObject.Evaluate("this.ToString()"))
コンテキスト オブジェクトの文字列表現を返します。
someObject.Execute(@
"Console.WriteLine(""Hello, world!"");
Console.WriteLine(""This demonstrates running a simple script"");
");
これらのステートメントをスクリプトとして実行するなどです。
実行可能ファイルは、例に示すように、ファクトリ メソッドを使用して簡単に取得できます。 ここ--必要なのは、ソース コードと、予期される名前付きパラメータのリストだけです (トークンは、string.Format() やハンドルバーとの衝突を避けるために、{{{0}}} などの三重括弧表記を使用して埋め込まれます)。構文のような):
IExecutable executable = ExecutableFactory.Default.GetExecutable(executableType, sourceCode, parameterNames, addedNamespaces);
各実行可能オブジェクト (スクリプトまたは式) はスレッドセーフであり、保存して再利用でき、スクリプト内からのロギングをサポートし、タイミング情報と最後の例外が発生した場合は保存します。安価なコピーを作成できるように、それぞれにコンパイルされた Copy() メソッドもあります。スクリプトまたは式からコンパイルされた実行可能オブジェクトを、他のものを作成するためのテンプレートとして使用します。
コンパイル済みのスクリプトまたはステートメントを実行するオーバーヘッドは比較的低く、適度なハードウェアではマイクロ秒未満であり、コンパイル済みのスクリプトと式は再利用のためにキャッシュされます。
構造体 (クラス) メンバーの値を名前で取得しようとしていました。構造は動的ではありませんでした。私が最終的にそれを得るまで、すべての答えは機能しませんでした。
public static object GetPropertyValue(object instance, string memberName)
{
return instance.GetType().GetField(memberName).GetValue(instance);
}
このメソッドは、メンバーの値を名前で返します。規則的な構造(クラス)で動作します。
をチェックするとよいでしょう Heleonix.リフレクション 図書館。これは、ネストされたメンバーを含むメンバーを動的に取得/設定/呼び出すためのメソッドを提供します。または、メンバーが明確に定義されている場合は、リフレクションよりも高速なゲッター/セッター (デリゲートにコンパイルされたラムダ) を作成できます。
var success = Reflector.Set(instance, null, $"Property{i}", value);
または、プロパティの数が無限ではない場合は、セッターを生成してキャッシュすることもできます (セッターはコンパイルされたデリゲートであるため、より高速です)。
var setter = Reflector.CreateSetter<object, object>($"Property{i}", typeof(type which contains "Property"+i));
setter(instance, value);
セッターは次のタイプにすることができます Action<object, object>
ただし、インスタンスは実行時に異なる可能性があるため、セッターのリストを作成できます。
残念ながら、C# には、あなたが求めていることを正確に実行するためのネイティブ機能はありません。
ただし、私の C# eval プログラムでは C# コードを評価できます。これは、実行時に C# コードを評価する機能を提供し、多くの C# ステートメントをサポートします。実際、このコードは任意の .NET プロジェクト内で使用できますが、使用できるのは C# 構文に限定されます。私のウェブサイトを見てください、 http://csharp-eval.com, 詳細については、を参照してください。
正解は、mem0ry の使用量を低く抑えるために、すべての結果をキャッシュする必要があるということです。
例は次のようになります
TypeOf(Evaluate)
{
"1+1":2;
"1+2":3;
"1+3":5;
....
"2-5":-3;
"0+0":1
}
そしてそれをリストに追加します
List<string> results = new List<string>();
for() results.Add(result);
IDを保存してコードで使用します
お役に立てれば