构建相互需要的代表

Constructing delegates that require each other

我正在尝试定义两个相互引用的委托,但我不确定最有效的方法是什么。我希望能够在没有任何隐式捕获的闭包的情况下做到这一点。

我有一些看起来有点像这样的东西:

Func<A, C> AtoC = null;
Func<B, C> BtoC = delegate(B b_in)
{
   //... uses some variables scoped to the outer method
   return b_in.getC() ?? AtoC( b_in.getA() );
}
AtoC  = delegate(A a_in)
{
   //... uses some variables scoped to the outer method
   return a_in.getC() ?? BtoC( a_in.getB() );
}

C final = AtoC(someA);

当然,真正的逻辑更复杂,但正如您所看到的,每个委托都引用了另一个委托,并且还隐式捕获了闭包。

我希望能够让工厂使用 "variables scoped to the outer method":

创建这些委托,从而使这些委托的构造更加明确
Func<A, C> AtoC = null;
Func<B, C> BtoC = MakeFunctionBtoC(someVariables, AtoC)
Func<A, C> AtoC = MakeFunctionAtoC(someVariables, BtoC)
C final = AtoC(someA);

//Elsewhere...
Func<B, C> MakeFunctionBtoC(someVariables, Func<A, C> AtoC)
{
    return delegate(B b_in)
    {
       //... uses someVariables
       return b_in.getC() ?? AtoC( b_in.getA() );
    }
}

从而显式捕获我希望用来构造委托的 'someVariables'。问题在于,使用此方法时,传递给 BtoC 工厂的函数 AtoC 在调用该方法时为 null,并且复制了该 null 值,因此即使稍后调用委托,函数 AtoC 从未被定义(就像在前一种情况下一样)。

有没有一种方法可以为委托使用工厂方法,同时让它们仍然相互依赖?

如果您希望将这两个方法的实际业务逻辑重构为它们自己的命名方法,您当然可以这样做。然后,您可以使用关闭委托的匿名方法来实际调用该命名方法:

Func<A, C> AtoC = null;
Func<B, C> BtoC = b => ConvertBToC(someVariables, AtoC);
AtoC = a => ConvertAtoC(someVariables, BtoC);
C final = AtoC(someA);

现在您可以将 ConvertBToC 编写为一个单独的命名方法,该方法可以接受 Func<A, C> 作为参数。它允许将每个对象到另一个对象的转换与两个委托及其循环引用的连接分开。当然它根本没有避免使用闭包;它只是让它们与业务逻辑分离。

如果你想真正避免使用闭包,正如你的问题在技术上要求的那样:

您可以添加另一层间接寻址;使用函数都可以引用的相关委托类型的字段创建可变引用类型:

public class Wrapper<T>
{
    public T Value { get; set; }
}

Func<B, C> MakeFunctionBtoC(someVariables, Wrapper<Func<A, C>> AtoC)
{
    return delegate(B b_in)
    {
       //... uses someVariables
       return b_in.getC() ?? AtoC.Value( b_in.getA() );
    }
}

当然,这在道德上等同于使用闭包时发生的情况,就像您在第一个解决方案中所做的那样(我觉得有必要指出您的第二个解决方案也在关闭变量,它们只是方法参数而不是局部变量)。

我不认为这比您原来的解决方案有任何优势,这对我来说似乎是迄今为止解决这个问题的最佳方式。

您的另一个选择是有效地进行编译器在看到闭包时会进行的重构,即创建一个新类型,创建一个可变字段来表示封闭变量,然后使用它而不是随处可见的封闭变量。但是,通过这样做而不是使用闭包,您只会真正让代码变得更复杂。