如何正确使用C# CallSite和CallSiteBinder进行动态方法调用,以便我正确利用调用站点缓存

问题描述 投票:0回答:1

在开发纯用 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 caching dynamic method-invocation
1个回答
0
投票

如果检查 .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));
© www.soinside.com 2019 - 2024. All rights reserved.