【问题标题】:Wrapping a WCF Call - emit capturing lambda or a better way?包装 WCF 调用 - 发出捕获 lambda 或更好的方法?
【发布时间】:2013-04-07 19:08:05
【问题描述】:

为了避免遇到 X-Y 问题,我想做的是包装 WCF 调用,以便自动实现重试(和其他规则),但我不知道前面的所有接口时间(这是一个中间件)。所以我基本上采用通用DuplexChannelFactory<TChannel>.CreateChannel() 的输出,然后再次使其成为代理。 SO上有几个关于包装调用和重试等的不同问题,但没有一个涉及你需要一个完全通用的解决方案的未知数量的接口。

所以我想在每次客户端调用事物时注入代码,但我希望我的对象直接实现TChannel接口。所以我想使用Reflection.Emit 子类化一个“基础”对象,该对象将保存DuplexChannelFactory<> 调用的结果,然后连接它自己的方法,包括重试功能。这是我在工厂方法中最初的“对象创建”:

    static TInterface generateImplementor<TInterface, Tcallback>(params object[] parameters) where TInterface : class
    {
        // Get the information about the interface.  This is necessary because this
        // is a generic method, where literally anything could be passed in
        Type interfaceType = typeof(TInterface);

        // Create the assembly and module to hold the object created
        // <snip>

        // Define a public class based on the passed-in interface name.
        TypeBuilder generatedType = myModule.DefineType(interfaceType.Name + "Implementor",
            TypeAttributes.Public, typeof(ForwarderBase<TInterface, Tcallback>),
            new Type[] { interfaceType });

        // Implement 'TInterface' interface.
        generatedType.AddInterfaceImplementation(interfaceType);

好的,但是从那里去哪里呢?这有点像我提出的静态编码,但我需要使用Reflection.Emit 进行最后两个调用。

class ForwarderBase<T, Tcallback>
{
    protected T proxyObj;
    public ForwarderBase(Tcallback callbackObj)
    {
        proxyObj = DuplexChannelFactory<T>.CreateChannel(callbackObj, "Endpoint");
    }

    private void noRetWrapper(Action call)
    {
        // Inject extra code here possibly
        try
        {
            call();
        }
        catch (Exception ex)
        {
            // all of this in a retry loop possibly, or whatever
            Console.WriteLine("Exception is: " + ex.ToString());
        }
    }

    private TRet retWrapper<TRet>(Func<TRet> call)
    {
        // Inject extra code here possibly
        try
        {
            return call();
        }
        catch (Exception ex)
        {
            // all of this in a retry loop possibly, or whatever
            Console.WriteLine("Exception is: " + ex.ToString());
        }
        return default(TRet);
    }

    // Dynamically emit these two, as depending on T, there will be an arbitrary number, with arbitrary arguments

    void firstMethod(int x)
    {
        // Lambda captures the arguments
        noRetWrapper(() => proxyObj.noReturnMethodCall(x));
    }

    int secondMethod(int firstParam, double secondParam, string thirdParam)
    {
        // Lambda captures the arguments
        return retWrapper(() => return proxyObj.returningMethodCall(firstParam, secondParam, thirdParam));
    }

}

所以我没有问题Emitting 最后两个方法(以及任何数量的方法,真的应该没问题),除了捕获 lambdas。这是必要的,否则上面的两个“包装器”会爆炸成任意数量的类型和返回值组合。

那么我如何Emit 捕获我需要的 lambda?正如this question 所说,没有Func&lt;...&gt; 之类的,因此我的一个Func 和一个Action 接近这里。

【问题讨论】:

  • 我不确定我是否理解你的问题......你不能用 Expression 构建一个表达式树然后调用 CompileToMethod(...) 吗?
  • 我不熟悉表达式树。有链接吗?还要记住,每个发出的方法上的 lambdas 都会略有不同(调用不同的方法,捕获不同的变量)。如果没问题,那就太好了,但我对表达式树一无所知。
  • 您能否添加一个答案 DarkSquirrell42 仅提供如何创建捕获所有参数的表达式树?基本上,如果我需要从我的示例中创建secondmethod(),我该怎么做?用表达式树做闭包的文档不容易找到。
  • 问题是:我不熟悉emit ...我的想法是在生成的类型中引入一个私有成员(成员类型可以由Expression.GetDelegateType()生成)来拥有一个保存你的闭包的字段......闭包本身需要访问实例,因此它不能在创建实例之前创建......这个想法是将该闭包称为某个持有委托的字段,因此可以在实例创建后初始化

标签: c# wcf lambda reflection.emit


【解决方案1】:

这就是Castle DynamicProxy(和类似的库)的用途。使用它,您可以编写一个拦截器类,每次调用代理上的方法时都会调用该拦截器类。该代理是通过调用方法自动创建的。

拦截器可能如下所示:

class IgnoreExceptionsInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            invocation.ReturnValue = GetDefault(invocation.Method.ReturnType);
        }
    }

    private static object GetDefault(Type type)
    {
        if (type.IsValueType && type != typeof(void))
        {
            return Activator.CreateInstance(type);
        }
        return null;
    }
}

(GetDefault() has to be used, because there is no direct equivalent of default(T) that would take a Type.)

使用接口IFoo 及其实现Foo,您可以这样使用它:

var generator = new ProxyGenerator();
IFoo fooProxy = generator.CreateInterfaceProxyWithTargetInterface<IFoo>(
    new Foo(), new IgnoreExceptionsInterceptor());
fooProxy.Whatever();

【讨论】:

  • 这也许值得一看,虽然文档...稀疏。具有目标接口的接口代理是我所需要的(因为我需要重新生成IChannel 但开关没有“粘住”的事实不是我需要的。见这里:kozmic.net/2009/04/27/… 除非那已经过时了. 有没有更好的文档?他们的主页链接到他们的 wiki,这并没有更好的资格。
  • @Kevin 不明白,开关不粘是什么意思?
  • 在我的链接的最后一段中以粗体显示,当您切换目标时,它只会针对该调用切换,而不是永久切换,并且实际上存在一个问题,如果您有多个调用一旦你必须非常小心它是如何工作的。
  • @Kevin 但是你为什么要改变目标呢?我认为您的情况不需要它,我的答案中的代码应该足够了。
  • 我没有给出“100% 完成”的例子。我还需要重试,以便在抛出异常时重新分配我的示例中的 proxyObj,然后再次尝试 lambda。这需要 DynamicProxy 的“带有目标接口的接口代理”版本,因此需要我提供的链接。
猜你喜欢
  • 1970-01-01
  • 2020-09-12
  • 1970-01-01
  • 2021-08-31
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
相关资源
最近更新 更多