C# 自反类型参数约束:X<;T>;式中T:X<;T>‒;还有更简单的选择吗?

C# 自反类型参数约束:X<;T>;式中T:X<;T>‒;还有更简单的选择吗?,c#,generics,crtp,self-reference,type-constraints,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
{
    ICloneable Clone();
}

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

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

接口IClonable,其中TImpl:IClonable
{
TImpl克隆();
}
羊类:易克隆
{
绵羊克隆(){…}
} //^^^^^
绵羊多莉=新绵羊().Clone();
主要优点:实现类型(例如
Sheep
)现在可以引用它自己而不是它的基类型,从而减少了类型转换的需要(如最后一行代码所示)

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

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


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

  • 声明
    X,其中T:X
    似乎是递归的,有人可能会想,为什么编译器会推理,“如果
    T
    X
    ,那么
    X
    实际上是
    X
    ”(但约束显然不会像这样得到解决)

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

  • 在混合中添加更多类型参数和各种泛型接口之间的子类型关系后,事情很快就会变得难以管理

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

虽然它可能看起来像是由引用自身的类型约束强制实现类型执行相同的操作,但实际上它并不是这样做的。人们使用此模式试图表示“此方法的重写必须返回重写类的类型”形式的模式,但这实际上不是类型系统表示或强制的约束。我举一个例子:

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

是的。我尽量避免这种模式。这很难讲道理

有人知道另一种C#代码模式可以达到相同的效果或类似的效果,但更容易掌握吗

如果你对这类事情感兴趣,你可以考虑看Haskell型系统。Haskell的“高级类型”可以表示这些类型模式

声明
X,其中T:X
似乎是递归的,人们可能会想,为什么编译器不会陷入无限循环中,推理,“如果
T
X
,那么
X
实际上是
X

在对这种简单关系进行推理时,编译器永远不会进入无限循环。然而,一般来说,具有反向变异的泛型的名义子类型是不可分的。有很多方法可以迫使编译器进行无限回归,而C#编译器在开始无限回归之前不会检测到并阻止这些回归。(不过,我希望在Roslyn编译器中添加此检测功能,但我们拭目以待。)

如果你对此感兴趣,请参阅我的文章。您还需要阅读链接到纸上的内容


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

换句话说,如果一头
Cow
实现了
ICloneable
,您仍然可以轻松地使
Sheep
实现
ICloneable

我会简单地使用
ICloneable
,没有任何限制,原因有两个:

  • 我严重怀疑您是否会犯使用错误类型参数的错误

  • 接口意味着对代码的其他部分进行收缩,而不是用于自动驾驶仪上的代码。如果代码的一部分需要
    ICloneable
    ,而您传递了一个
    Sheep
    ,它可以做到这一点,那么从这一点来看,它似乎是完全有效的


  • 您会很高兴知道,这是一个非常常见的名称:它被称为“奇怪的重复模板模式”(简称CRTP)。。。。它与约束无关(标准C++模板根本没有它们)。是的,因为没有约束,可以实现<代码>羊<代码> >代码>类羊:ICLunEnter“公共狗克隆”({…} } /代码>这可能不是人们最初对
    ICloneable
    界面的想法。@stakx当时我只是四处跑,在空中挥舞着双手:)上周的一个相关问题谢谢你的详细回答。你的博客文章一针见血+我还指出,C#编译器在显然是无限的东西方面比博格集合更聪明+1“[
    interface X,其中T:X{}
    ]仅将可能的参数限制为自己实现它的类,这并不意味着它们就是当前正在实现的类。”非常简洁,非常出色!
    interface ICloneable<TImpl> where TImpl : ICloneable<TImpl>
    {
        TImpl Clone();
    }
    
    class Sheep : ICloneable<Sheep>
    {
        Sheep Clone() { … }
    } //^^^^^
    
    Sheep dolly = new Sheep().Clone();