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
- 在混合中添加更多类型参数和各种泛型接口之间的子类型关系后,事情很快就会变得难以管理
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();