自反型参数约束:X <t>其中T:X <t> - 任何更简单的替代方案?</t> </t>

时间:2012-01-14 22:57:16

标签: c# generics crtp self-reference type-constraints

我经常通过向其添加自引用(“反身”)类型参数约束来使简单的界面变得更复杂。例如,我可能会这样做:

interface ICloneable
{
    ICloneable Clone();
}

class Sheep : ICloneable
{
    ICloneable Clone() { … }
} //^^^^^^^^^^

Sheep dolly = new Sheep().Clone() as Sheep;
                                //^^^^^^^^

成:

interface ICloneable<TImpl> where TImpl : ICloneable<TImpl>
{
    TImpl Clone();
}

class Sheep : ICloneable<Sheep>
{
    Sheep Clone() { … }
} //^^^^^

Sheep dolly = new Sheep().Clone();

主要优点:实现类型(例如Sheep)现在可以引用自身而不是其基类型,从而减少了对类型转换的需求(如最后一行代码所示)。

虽然这非常好,但我也注意到这些类型参数约束不直观,并且在更复杂的场景中很难理解。 *) < / SUP>

问题:是否有人知道另一种C#代码模式可以达到相同的效果或相似之处,但是更容易掌握?


  

*)此代码模式可能不直观且难以理解,例如以这些方式:

     
      
  • 声明X<T> where T : X<T>似乎是递归的,人们可能想知道为什么编译器doesn't get stuck in an infinite loop,推理,“如果T是{{ 1}},然后X<T>实际上是X<T>。“(但是约束显然不会像那样得到解决。)

  •   
  • 对于实施者来说,可能不清楚应该指定哪种类型来代替X<X<…<T>…>>。 (约束最终会解决这个问题。)

  •   
  • 一旦你在混合中添加了更多类型参数和各种通用接口之间的子类型关系,事情就会很快变得无法管理。

  •   

2 个答案:

答案 0 :(得分:19)

  

主要优点:实现类型现在可以引用自身而不是基类型,从而减少了对类型转换的需求

虽然看起来似乎是引用本身的类型约束,但是它强制实现类型执行相同的操作,实际上并不是它的功能。人们使用这种模式来尝试表达形式的模式“这种方法的覆盖必须返回覆盖类的类型”,但这实际上并不是类型系统表达或强制执行的约束。我在这里举个例子:

http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

  

虽然这非常好,但我也注意到这些类型参数约束不直观,并且在更复杂的场景中变得非常难以理解

是的。我尽量避免这种模式。这很难说清楚。

  

有没有人知道另一种C#代码模式可以实现相同的效果或类似的东西,但是更容易掌握?

不在C#中,没有。如果这种事情让你感兴趣,你可以考虑看看Haskell类型系统; Haskell的“更高类型”可以代表那种类型的模式。

  

声明X<T> where T : X<T>似乎是递归的,有人可能想知道为什么编译器不会陷入无限循环,推理​​,“如果TX<T>,那么X<T>实际上是X<X<…<T>…>>。“

在推理这种简单关系时,编译器不会进入无限循环。但是,具有逆变的泛型类型的名义子类型通常是不可靠的。有一些方法可以强制编译器进入无限回归,而C#编译器在开始无限旅程之前不会检测到这些并阻止它们。 (然而。我希望在Roslyn编译器中为此添加检测,但我们会看到。)

如果您对此感兴趣,请参阅我关于此主题的文章。你也想阅读链接到论文。

http://blogs.msdn.com/b/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx

答案 1 :(得分:6)

不幸的是,没有办法完全阻止这种情况,没有类型约束的通用ICloneable<T>就足够了。您的约束仅将可能的参数限制为自己实现它的类,这并不意味着它们是当前正在实现的类。

换句话说,如果Cow实施ICloneable<Cow>,您仍然可以轻松Sheep实施ICloneable<Cow>

我只是在没有约束的情况下使用ICloneable<T>有两个原因:

  1. 我严重怀疑你会错误地使用错误的类型参数。

  2. 对于其他代码部分,接口应该是合同,而不是用于在自动驾驶上进行编码。如果代码的一部分需要ICloneable<Cow>并且您传递的Sheep可以做到这一点,那么从那时起它似乎完全有效。