在开发纯用 C# 编写的 DSL 的自定义解释器时,我试图了解如何正确利用内置的调用站点缓存功能。这是我的小演示程序,它仅在类型更改之前有效。
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
public class Program2
{
public static void Main()
{
// Create a CallSite
var callSite = CallSite<Func<CallSite, object, object, object>>.Create(new AdditionBinder());
// Perform dynamic addition
var add = callSite.Target;
var result1 = add(callSite, 5, 10); // Bind for int,int gets called for the first time and compiled fn(int,int) is created
var result2 = add(callSite, 10, 10); // Bind not called, the compiled fn(int,int) is reused. Nice!
var result3 = add(callSite, "strA", "str1"); // before Bind was even called I got exception "Unable to cast object of type 'System.String' to type 'System.Int32" :-(
var result4 = add(callSite, "strB", "str2");
}
}
以下是自定义活页夹的实现方式。 如何切换类型的逻辑很简单,并且在我确保每个类型组合仅正确调用此代码一次之后会更好。 整个csproj就没有别的了。
public class AdditionBinder : CallSiteBinder
{
public override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel)
{
// Get the two arguments that we're adding
var left = parameters[0];
var right = parameters[1];
// Here comes later logic how to prepare for tailormade type specific compiled variants
Expression addition = null;
if (args[0] is int && args[1] is int)
{
// Perform int addition
addition = Expression.Convert( Expression.Add(
Expression.Convert(left, typeof(int)),
Expression.Convert(right, typeof(int))
), typeof(object)
);
}
else if (args[0] is string && args[1] is string)
{
// Perform string addition
addition = Expression.Convert(
Expression.Add(
Expression.Convert(left, typeof(string)),
Expression.Convert(right, typeof(string)),
typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) })
), typeof(object)
);
}
else
throw new InvalidOperationException("No add for T + T");
// Return the result of the addition
return Expression.Return(returnLabel, addition);
}
}
我已经调试到 CallSiteBinder.cs,但我不清楚我是否理解它并且我已经正确下载了所有必要的部分。
我是否正确使用 Binder? 我是否正确使用了目标或调用站点? 我感觉我真的很亲近。 我想在这里发布这个,因为我从未在网上找到完整的示例。 我希望我们能解决这个问题,并且此页面可以帮助其他人。 谢谢你的好意建议。
我尝试了共享活页夹的两个不同的 CallSite 实例,并得到了这个:
var sharedBinder = new AdditionBinder();
var callSite = CallSite<Func<CallSite, object, object, object>>.Create(sharedBinder);
var callSite2 = CallSite<Func<CallSite, object, object, object>>.Create(sharedBinder);
_ = callSite2.Target(callSite2, "str", "str"); // works
_ = callSite.Target(callSite, 5, 10); // throws as if the callsite instances shared the cached Target
我知道我正在使用的内部组件可能不适合核心团队之外的开发人员使用。但我仍然认为了解其工作原理很有趣,并且可能有助于许多其他口译作家的表现。
如果检查 .NET 运行时(或 .NET Framework 参考源)的来源,
CallSite<T>
在 private static T
字段中兑现 T 委托:
...
private static T? s_cachedUpdate;
private CallSite(CallSiteBinder binder)
: base(binder)
{
Target = GetUpdateDelegate();
}
private T GetUpdateDelegate()
{
return GetUpdateDelegate(ref s_cachedUpdate);
}
private T GetUpdateDelegate(ref T? addr) =>
addr ??= MakeUpdateDelegate();
因此
CallSite<Func<CallSite, object, object, object>>.Create(*)
将始终返回相同的缓存实例。
要生成委托的不同实例,您可以使用不同的泛型类型参数(例如特定于类型的泛型,如下面的
CallSite<Func<CallSite, int, int, int>>.Create(...)
和 CallSite<Func<CallSite, string, string, string>>.Create(...)
)。但这将是一个编译时调度,并且考虑到原始代码有 object
,编译时调度可能不是问题所在。
var sharedBinder = new AdditionBinder();
var callSite = CallSite<Func<CallSite, int, int, int>>.Create(sharedBinder);
var callSite2 = CallSite<Func<CallSite, string, string, string>>.Create(sharedBinder);
...
public class AdditionBinder : CallSiteBinder
{
public override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel)
{
...
if (args[0] is int && args[1] is int)
{
// Perform int addition
addition = Expression.Convert( Expression.Add(
Expression.Convert(left, typeof(int)),
Expression.Convert(right, typeof(int))
), typeof(int)
);
}
else if (args[0] is string && args[1] is string)
{
// Perform string addition
addition = Expression.Convert(
Expression.Add(
Expression.Convert(left, typeof(string)),
Expression.Convert(right, typeof(string)),
typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) })
), typeof(string)
);
}
...
或者可以使用运行时调度:
var sharedBinder = new AdditionBinder();
var callSite = CallSite<Func<CallSite, object, object, object>>.Create(sharedBinder);
var callSite2 = CallSite<Func<CallSite, object, object, object>>.Create(sharedBinder);
...
public class AdditionBinder : CallSiteBinder
{
public override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel)
{
// Get the two arguments that we're adding
var left = parameters[0];
var right = parameters[1];
var ifInteger = Expression.AndAlso(
Expression.TypeIs(left, typeof(int)),
Expression.TypeIs(right, typeof(int)));
var integerAddition = Expression.Convert( Expression.Add(
Expression.Convert(left, typeof(int)),
Expression.Convert(right, typeof(int))
), typeof(object)
);
var ifString = Expression.AndAlso(
Expression.TypeIs(left, typeof(string)),
Expression.TypeIs(right, typeof(string)));
var stringAddition = Expression.Convert(
Expression.Add(
Expression.Convert(left, typeof(string)),
Expression.Convert(right, typeof(string)),
typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) })
), typeof(object)
);
var throwExpression = Expression.Throw(
Expression.Constant(new InvalidOperationException("No add for T + T")));
return Expression.IfThenElse(
ifInteger,
Expression.Return(returnLabel, integerAddition),
Expression.IfThenElse(
ifString,
Expression.Return(returnLabel, stringAddition),
throwExpression));