文字列からC#で匿名メソッドを作成することは可能ですか?
たとえば、文字列"x + y * z"
がある場合、これを何らかのメソッド/ラムダ オブジェクトに変換して、任意x
の , y
,z
パラメータで呼び出すことができますか?
文字列からC#で匿名メソッドを作成することは可能ですか?
たとえば、文字列"x + y * z"
がある場合、これを何らかのメソッド/ラムダ オブジェクトに変換して、任意x
の , y
,z
パラメータで呼び出すことができますか?
可能です、はい。文字列を解析し、たとえば、式ツリーを使用してデリゲートをコンパイルする必要があります。
(x, y, z) => x + y * z
式ツリーを使用して作成する例を次に示します。
ParameterExpression parameterX = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterY = Expression.Parameter(typeof(int), "y");
ParameterExpression parameterZ = Expression.Parameter(typeof(int), "z");
Expression multiplyYZ = Expression.Multiply(parameterY, parameterZ);
Expression addXMultiplyYZ = Expression.Add(parameterX, multiplyYZ);
Func<int,int,int,int> f = Expression.Lambda<Func<int, int, int, int>>
(
addXMultiplyYZ,
parameterX,
parameterY,
parameterZ
).Compile();
Console.WriteLine(f(24, 6, 3)); // prints 42 to the console
CodeDom を使用する楽しみのために (mscorlib に存在する限り、有効な C# コードは文字列で許可されます (エラーのチェックはまったく行われません)。
static class Program
{
static string code = @"
public static class __CompiledExpr__
{{
public static {0} Run({1})
{{
return {2};
}}
}}
";
static MethodInfo ToMethod(string expr, Type[] argTypes, string[] argNames, Type resultType)
{
StringBuilder argString = new StringBuilder();
for (int i = 0; i < argTypes.Length; i++)
{
if (i != 0) argString.Append(", ");
argString.AppendFormat("{0} {1}", argTypes[i].FullName, argNames[i]);
}
string finalCode = string.Format(code, resultType != null ? resultType.FullName : "void",
argString, expr);
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("mscorlib.dll");
parameters.ReferencedAssemblies.Add(Path.GetFileName(Assembly.GetExecutingAssembly().Location));
parameters.GenerateInMemory = true;
var c = new CSharpCodeProvider();
CompilerResults results = c.CompileAssemblyFromSource(parameters, finalCode);
var asm = results.CompiledAssembly;
var compiledType = asm.GetType("__CompiledExpr__");
return compiledType.GetMethod("Run");
}
static Action ToAction(this string expr)
{
var method = ToMethod(expr, new Type[0], new string[0], null);
return () => method.Invoke(null, new object[0]);
}
static Func<TResult> ToFunc<TResult>(this string expr)
{
var method = ToMethod(expr, new Type[0], new string[0], typeof(TResult));
return () => (TResult)method.Invoke(null, new object[0]);
}
static Func<T, TResult> ToFunc<T, TResult>(this string expr, string arg1Name)
{
var method = ToMethod(expr, new Type[] { typeof(T) }, new string[] { arg1Name }, typeof(TResult));
return (T arg1) => (TResult)method.Invoke(null, new object[] { arg1 });
}
static Func<T1, T2, TResult> ToFunc<T1, T2, TResult>(this string expr, string arg1Name, string arg2Name)
{
var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2) },
new string[] { arg1Name, arg2Name }, typeof(TResult));
return (T1 arg1, T2 arg2) => (TResult)method.Invoke(null, new object[] { arg1, arg2 });
}
static Func<T1, T2, T3, TResult> ToFunc<T1, T2, T3, TResult>(this string expr, string arg1Name, string arg2Name, string arg3Name)
{
var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2), typeof(T3) },
new string[] { arg1Name, arg2Name, arg3Name }, typeof(TResult));
return (T1 arg1, T2 arg2, T3 arg3) => (TResult)method.Invoke(null, new object[] { arg1, arg2, arg3 });
}
static void Main(string[] args)
{
var f = "x + y * z".ToFunc<int, int, long, long>("x", "y", "z");
var x = f(3, 6, 8);
}
}
C# にはこのような機能はありません (JavaScript などの他の言語には、このようなeval
機能を処理する関数があります)。文字列を解析し、式ツリーを使用するか、IL を発行してメソッドを自分で作成する必要があります。
.Net フレームワークでこれを行う機能があります。
それは簡単ではない。呼び出すことができるクラスとメソッドを含む完全なアセンブリにするために、ステートメントの周りにいくつかのコードを追加する必要があります。
その後、文字列をに渡します
CSharpCodeProvider.CompileAssemblyFromSource(options, yourcode);
文法 (例: ANTLR) と、式ツリーを作成するインタープリターを使用すると可能です。これは簡単な作業ではありませんが、入力として受け入れる範囲を制限すれば成功する可能性があります。ここにいくつかの参照があります:
以下は、ANTLR ITree を Expression ツリーに変換するコードの例です。それは完全ではありませんが、あなたが何に反対しているかを示しています。
private Dictionary<string, ParameterExpression> variables
= new Dictionary<string, ParameterExpression>();
public Expression Visit(ITree tree)
{
switch(tree.Type)
{
case MyParser.NUMBER_LITERAL:
{
float value;
var literal = tree.GetChild(0).Text;
if (!Single.TryParse(literal, out value))
throw new MyParserException("Invalid number literal");
return Expression.Constant(value);
}
case MyParser.IDENTIFIER:
{
var ident = tree.GetChild(0).Text;
if (!this.variables.ContainsKey(ident))
{
this.variables.Add(ident,
Expression.Parameter(typeof(float), ident));
}
return this.variables[ident];
}
case MyParser.ADD_EXPR:
return Expression.Add(Visit(tree.GetChild(0)), Visit(tree.GetChild(1)));
// ... more here
}
}