C# 使用.NET 3.5调用多个表达式 替代解决方案
虽然我(对于这个项目来说)仅限于.NET3.5,但我已经成功地使用了ExpressionTrees的DLR版本。它是在Apache许可证版本2.0下发布的 这增加了对所有表达式的支持(可能更多或更少,但可能不是)。NET 4.0+表达式,如C# 使用.NET 3.5调用多个表达式 替代解决方案,c#,performance,lambda,expression-trees,C#,Performance,Lambda,Expression Trees,虽然我(对于这个项目来说)仅限于.NET3.5,但我已经成功地使用了ExpressionTrees的DLR版本。它是在Apache许可证版本2.0下发布的 这增加了对所有表达式的支持(可能更多或更少,但可能不是)。NET 4.0+表达式,如BlockExpression,我需要这些表达式来回答这个问题 原始问题 在我当前的项目中,我正在编译一个带有可变数量参数的表达式树。我有一系列需要调用的表达式。在.NET4.0+中,我只想使用Expression.Block来实现这一点,但是,我仅限于在
BlockExpression
,我需要这些表达式来回答这个问题
原始问题 在我当前的项目中,我正在编译一个带有可变数量参数的表达式树。我有一系列需要调用的
表达式。在.NET4.0+中,我只想使用Expression.Block
来实现这一点,但是,我仅限于在这个项目中使用.NET3.5
现在我发现了一个解决这个问题的方法,但我不认为这是解决这个问题的最好方法
守则:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Program
{
struct Complex
{
public float Real;
public float Imaginary;
}
// Passed to all processing functions
class ProcessContext
{
public ConsoleColor CurrentColor;
}
// Process functions. Write to console as example.
static void processString(ProcessContext ctx, string s)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("String: " + s); }
static void processAltString(ProcessContext ctx, string s)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("AltString: " + s); }
static void processInt(ProcessContext ctx, int i)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("Int32: " + i); }
static void processComplex(ProcessContext ctx, Complex c)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("Complex: " + c.Real + " + " + c.Imaginary + "i"); }
// Using delegates to access MethodInfo, just to simplify example.
static readonly MethodInfo _processString = new Action<ProcessContext, string>(processString).Method;
static readonly MethodInfo _processAltString = new Action<ProcessContext, string>(processAltString).Method;
static readonly MethodInfo _processInt = new Action<ProcessContext, int>(processInt).Method;
static readonly MethodInfo _processComplex = new Action<ProcessContext, Complex>(processComplex).Method;
static void Main(string[] args)
{
var methodNet40 = genNet40();
var methodNet35 = genNet35();
var ctx = new ProcessContext();
ctx.CurrentColor = ConsoleColor.Red;
methodNet40(ctx, "string1", "string2", 101, new Complex { Real = 5f, Imaginary = 10f });
methodNet35(ctx, "string1", "string2", 101, new Complex { Real = 5f, Imaginary = 10f });
// Both work and print in red:
// String: string1
// AltString: string2
// Int32: 101
// Complex: 5 + 10i
}
static void commonSetup(out ParameterExpression pCtx, out ParameterExpression[] parameters, out Expression[] processMethods)
{
pCtx = Expression.Parameter(typeof(ProcessContext), "pCtx");
// Hard-coded for simplicity. In the actual code these are reflected.
parameters = new ParameterExpression[]
{
// Two strings, just to indicate that the process method
// can be different between the same types.
Expression.Parameter(typeof(string), "pString"),
Expression.Parameter(typeof(string), "pAltString"),
Expression.Parameter(typeof(int), "pInt32"),
Expression.Parameter(typeof(Complex), "pComplex")
};
// Again hard-coded. In the actual code these are also reflected.
processMethods = new Expression[]
{
Expression.Call(_processString, pCtx, parameters[0]),
Expression.Call(_processAltString, pCtx, parameters[1]),
Expression.Call(_processInt, pCtx, parameters[2]),
Expression.Call(_processComplex, pCtx, parameters[3]),
};
}
static Action<ProcessContext, string, string, int, Complex> genNet40()
{
ParameterExpression pCtx;
ParameterExpression[] parameters;
Expression[] processMethods;
commonSetup(out pCtx, out parameters, out processMethods);
// What I'd do in .NET 4.0+
var lambdaParams = new ParameterExpression[parameters.Length + 1]; // Add ctx
lambdaParams[0] = pCtx;
Array.Copy(parameters, 0, lambdaParams, 1, parameters.Length);
var method = Expression.Lambda<Action<ProcessContext, string, string, int, Complex>>(
Expression.Block(processMethods),
lambdaParams).Compile();
return method;
}
static Action<ProcessContext, string, string, int, Complex> genNet35()
{
ParameterExpression pCtx;
ParameterExpression[] parameters;
Expression[] processMethods;
commonSetup(out pCtx, out parameters, out processMethods);
// Due to the lack of the Block expression, the only way I found to execute
// a method and pass the Expressions as its parameters. The problem however is
// that the processing methods return void, it can therefore not be passed as
// a parameter to an object.
// The only functional way I found, by generating a method for each call,
// then passing that as an argument to a generic Action<T> invoker with
// parameter T that returns null. A super dirty probably inefficient hack.
// Get reference to the invoke helper
MethodInfo invokeHelper =
typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(x => x.Name == "invokeHelper" && x.IsGenericMethodDefinition);
// Route each processMethod through invokeHelper<T>
for (int i = 0; i < processMethods.Length; i++)
{
// Get some references
ParameterExpression param = parameters[i];
Expression process = processMethods[i];
// Compile the old process to Action<T>
Type delegateType = typeof(Action<,>).MakeGenericType(pCtx.Type, param.Type);
Delegate compiledProcess = Expression.Lambda(delegateType, process, pCtx, param).Compile();
// Create a new expression that routes the Action<T> through invokeHelper<T>
processMethods[i] = Expression.Call(
invokeHelper.MakeGenericMethod(param.Type),
Expression.Constant(compiledProcess, delegateType),
pCtx, param);
}
// Now processMethods execute and then return null, so we can use it as parameter
// for any function. Get the MethodInfo through a delegate.
MethodInfo call2Helper = new Func<object, object, object>(Program.call2Helper).Method;
// Start with the last call
Expression lambdaBody = Expression.Call(call2Helper,
processMethods[processMethods.Length - 1],
Expression.Constant(null, typeof(object)));
// Then add all the previous calls
for (int i = processMethods.Length - 2; i >= 0; i--)
{
lambdaBody = Expression.Call(call2Helper,
processMethods[i],
lambdaBody);
}
var lambdaParams = new ParameterExpression[parameters.Length + 1]; // Add ctx
lambdaParams[0] = pCtx;
Array.Copy(parameters, 0, lambdaParams, 1, parameters.Length);
var method = Expression.Lambda<Action<ProcessContext, string, string, int, Complex>>(
lambdaBody,
lambdaParams).Compile();
return method;
}
static object invokeHelper<T>(Action<ProcessContext, T> method, ProcessContext ctx, T parameter)
{
method(ctx, parameter);
return null;
}
static object call2Helper(object p1, object p2) { return null; }
}
使用系统;
使用System.Linq;
使用System.Linq.Expressions;
运用系统反思;
班级计划
{
结构复合体
{
公共浮动房地产;
公共交通;
}
//传递给所有处理函数
类ProcessContext
{
公共控制台颜色CurrentColor;
}
//处理函数。以写入控制台为例。
静态无效processString(ProcessContext ctx,字符串s)
{Console.ForegroundColor=ctx.CurrentColor;Console.WriteLine(“字符串:+s);}
静态无效processAltString(ProcessContext ctx,字符串s)
{Console.ForegroundColor=ctx.CurrentColor;Console.WriteLine(“AltString:+s);}
静态void processInt(ProcessContext ctx,inti)
{Console.ForegroundColor=ctx.CurrentColor;Console.WriteLine(“Int32:+i);}
静态void processComplex(ProcessContext ctx,Complex c)
{Console.ForegroundColor=ctx.CurrentColor;Console.WriteLine(“复数:“+c.Real+”+“+”+c.virtual+“i”);}
//使用委托访问MethodInfo,只是为了简化示例。
静态只读MethodInfo _processString=新操作(processString).Method;
静态只读MethodInfo\u processAltString=新操作(processAltString).Method;
静态只读MethodInfo _processInt=新操作(processInt).Method;
静态只读MethodInfo _processComplex=新操作(processComplex).Method;
静态void Main(字符串[]参数)
{
var methodNet40=genNet40();
var methodNet35=genNet35();
var ctx=new ProcessContext();
ctx.CurrentColor=ConsoleColor.Red;
methodNet40(ctx,“string1”,“string2”,101,新复数{实=5f,虚=10f});
methodNet35(ctx,“string1”,“string2”,101,新复合物{实=5f,虚=10f});
//工作和打印均为红色:
//字符串:string1
//AltString:string2
//Int32:101
//复合体:5+10i
}
静态void commonSetup(out ParameterExpression pCtx、out ParameterExpression[]参数、out Expression[]processMethods)
{
pCtx=表达式.参数(typeof(ProcessContext),“pCtx”);
//为简单起见,硬编码。在实际代码中,这些都会反映出来。
参数=新参数表达式[]
{
//两个字符串,仅表示进程方法
//在相同类型之间可以不同。
Expression.Parameter(typeof(string),“pString”),
Expression.Parameter(typeof(string),“pAltString”),
表达式参数(typeof(int),“pInt32”),
表达式参数(typeof(Complex),“pComplex”)
};
//同样是硬编码的。在实际代码中也反映了这些。
processMethods=新表达式[]
{
Expression.Call(_processString,pCtx,参数[0]),
Expression.Call(_processAltString,pCtx,parameters[1]),
Expression.Call(_processInt,pCtx,parameters[2]),
Expression.Call(_processComplex,pCtx,parameters[3]),
};
}
静态作用genNet40()
{
参数表达pCtx;
ParameterExpression[]参数;
表达方法;
公共设置(输出pCtx、输出参数、输出处理方法);
//在.NET4.0中我会做什么+
var lambdaParams=新参数表达式[parameters.Length+1];//添加ctx
λ参数[0]=pCtx;
复制(参数,0,lambdarams,1,参数.Length);
var method=Expression.Lambda(
Expression.Block(processMethods),
lambdaParams.Compile();
返回法;
}
静态作用genNet35()
{
参数表达pCtx;
ParameterExpression[]参数;
表达方法;
公共设置(输出pCtx、输出参数、输出处理方法);
//由于缺少块表达式,我发现执行
//方法,并将表达式作为其参数传递
//如果处理方法返回void,则不能将其作为
//对象的参数。
//我发现的唯一功能性方法是为每个调用生成一个方法,
//然后将其作为参数传递给具有
//返回null的参数T。一个超级脏的可能是低效的黑客。
//获取对调用帮助程序的引用
MethodInfo调用帮助程序=
typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(x=>x.Name==“invokeHelper”&&x.IsGenericMethodDefinition);
//通过invokeHelper路由每个processMethod
for(int i=0;istatic object Dummy(object o1, object o2, object o3, ...) { return null; }
Dummy(func1(), func2(), func3());
//TODO come up with a better name
public class Foo
{
private static void InvokeAll(Action[] actions)
{
foreach (var action in actions)
action();
}
public static Expression Block(IEnumerable<Expression> expressions)
{
var invokeMethod = typeof(Foo).GetMethod("InvokeAll",
BindingFlags.Static | BindingFlags.NonPublic);
var actions = expressions.Select(e => Expression.Lambda<Action>(e))
.ToArray();
var arrayOfActions = Expression.NewArrayInit(typeof(Action), actions);
return Expression.Call(invokeMethod, arrayOfActions);
}
}