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

sta*_*ica 18 c# generics crtp type-constraints self-reference

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

interface ICloneable
{
    ICloneable Clone();
}

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

Sheep dolly = new Sheep().Clone() as Sheep;
                                //^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

成:

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

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

Sheep dolly = new Sheep().Clone();
Run Code Online (Sandbox Code Playgroud)

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

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

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


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

  • 声明X<T> where T : X<T>似乎是递归的,人们可能想知道为什么编译器不会陷入无限循环,推理,"如果TX<T>,那么X<T>实际上是一个X<X<…<T>…>>." (但是限制显然不会像那样得到解决.)

  • 对于实现者来说,可能不明确应该指定什么类型TImpl.(约束最终会解决这个问题.)

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

Eri*_*ert 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


Gro*_*roo 6

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

换句话说,如果是一个Cow工具ICloneable<Cow>,你仍然可以轻松地Sheep实现ICloneable<Cow>.

我会简单地使用ICloneable<T>没有约束的原因有两个:

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

  2. 接口意味着是代码的其他部分的合同,而不是用于在自动驾驶仪上编码.如果代码的一部分需要ICloneable<Cow>并且您传递了一个Sheep可以执行此操作的代码,那么从那一点看它似乎完全有效.