“Classname<T> where T: Classname<T>”是什么意思?

26

我正在阅读有关原型模式的德语维基百科文章。示例部分包含一个通用的C#实现,使用以下内容:

abstract class Prototype<T> where T : Prototype<T> { ... }
...
class ConcretePrototype : Prototype<ConcretePrototype> { ... }

这是怎么实现的?如何将T限制为相同的泛型类?如何使用一个类来从自身派生出一个泛型类型?
我虽然不会编写C#,但这个问题似乎很有趣。
来源:原型模式

4
这是F-bounded多态的一个例子。在C++中,它被称为奇异递归模板模式(CRTP) - Patryk Ćwiek
8
Eric Lippert讨论了C#中这种模式的优缺点:越来越好奇 - Damien_The_Unbeliever
1
基本上,这是C#中缺少"MyType"功能的解决方法。而且许多其他语言也是如此,因为当前主流语言都没有MyTypes。 - Jörg W Mittag
3个回答

12

ProtoType<T>有一个Clone方法,以类型安全的方式返回具体原型,因此T必须被定义为类型参数。由于T的类型必须只是派生自Prototype的类,因此该行代码为:

abstract class Prototype<T> where T : Prototype<T> { ... }

需要将 T 限制为 Prototype 的子类。由于 Prototype 是泛型的,因此必须在限制中指定 Prototype<T>

理论上,ConcretePrototype 的声明应该只是:

class ConcretePrototype : Prototype<> { ... }

(或类似的语法)。但是C#编译器不支持以这种方式推断类型参数。如果您输入以下内容:

class ConcretePrototype : Prototype<string> { ... }

如果你没有显式地声明 Prototype<ConcretePrototype>,编译器会提示编译错误,因为它知道必须这样做,这是由于 Prototype 的限制所导致的。尽管如此,编译器仍然需要显式声明:

class ConcretePrototype : Prototype<ConcretePrototype> { ... }

我注意到Damien_The_Unbeliever已经找到了相关的参考资料,但是我还是想提一下Eric Lippert关于这个主题的优秀文章。读这篇文章绝对值得,它可以帮助我们更好地理解这个主题并明白它为什么会引起问题。


Eric Lippert的帖子真的很有启发性。实际上,这个模式解决了我们在大学编程课程中曾经遇到的一个练习,但是在阅读和理解他的帖子之后,我会避免使用它。 - Luca Fülbier
博客文章链接已失效。 - Dmitri Nesteruk

4

基本上,这只是限制TypeParameter T为继承自Prototype的类型,并将其自身的类型作为TypeParameter。

因此,只有继承自Prototype<T>的类可以作为T传递。

(可能)的工作示例:

class FirstConcretePrototype : Prototype<FirstConcretePrototype> { } // works

// propably not what the author wanted to happen but...
class SecondConcretePrototype : Prototype<FirstConcretePrototype> { } // works (at least compiles) too, funny huh?

请注意,SecondConcretePrototype 是有效的 C# 代码,但可能会失败,因为 TFirstConcretePrototype,在 Prototype<T>Clone 方法中,this 对象(类型为 SecondConcretePrototype)被强制转换为 FirstConcretePrototype。由于这种转换不可能,因此它在运行时总是失败的,在 SecondConcretePrototype 中。请注意保留 HTML 标记,但不要写解释。
public T Clone()
{
    return (T)this.MemberwiseClone();
}

翻译成

// still in SecondConcretePrototype ...
public FirstConcretePrototype Clone()
{
    return (FirstConcretePrototype)this.MemberwiseClone(); // 'this' is of type SecondConcretePrototype 
}

我知道这不是一个正常人会打的东西,但值得指出并且我认为这种模式有些“不干净”,因为 Typerestriction 不能保护你免受糟糕的事情的影响。

失败的示例

class AnyType{ }

class ThirdConcretePrototype : Prototype<AnyType> { } // fails at compiletime, AnyType does not inhertit from Prototype<T>

除了这种模式非常不直观之外,你的第一个例子在我看来使情况变得更糟。我更喜欢英文维基百科文章中的实现方式,其中具体原型只是克隆到原型实例。 - Luca Fülbier

2
我会尝试解释一下,但首先让我们看一个简短而有效的例子:
abstract class Prototype<T> where T : Prototype<T> 
{
    public T Clone()
    {
        return this.MemberwiseClone() as T;
    }
}

class ConcretePrototype1 : Prototype<ConcretePrototype1> 
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class ConcretePrototype2 : Prototype<ConcretePrototype2> 
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        ConcretePrototype1 inst1 = new ConcretePrototype1()
        {
            Id = 1,
            Name = "Jon Skeet"
        };

        ConcretePrototype2 inst2 = new ConcretePrototype2()
        {
            Id = 2,
            Name = "Frodo Torbins"
        };

        ConcretePrototype1 copy1 = inst1.Clone();
        ConcretePrototype2 copy2 = inst2.Clone();

        Console.WriteLine(copy1.Name + "  " + copy1.GetType().Name);
        Console.WriteLine(copy2.Name + "  " + copy2.GetType().Name);
    }
} 

// 输出

Jon Skeet   ConcretePrototype1
Frodo Torbins ConcretePrototype2

解释:

这个是什么意思?

正如您所看到的,原型模式只有一个方法 Clone(),它可以产生当前对象的副本。

你如何将 T 限制为相同的泛型类?

没有理由不将类型参数限制为与此基础抽象类继承相同类或派生类。这将导致类似于 Prototype<Prototype<T>>Prototype<Derived<T>> 的结果(假设 Derived 继承自 Prototype 类)。

你怎样用一个类使用自身来派生一个泛型类型?

当我们声明 ConcretePrototype1 类时,我们将其从 Prototype<ConcretePrototype1>(自身)派生,因此我们让编译器知道 Prototype<T> 模式应该使用 ConcretePrototype1 作为其 T 参数。 这导致方法 Clone() 返回 ConcretePrototype1 实例,因为它就是我们的 T。对于 ConcretePrototype2 类也适用相同的逻辑。

因此,这个抽象类签名的 Prototype 模式如下:

abstract class Prototype<T> where T : Prototype<T> 

限制其Clone()方法仅生成派生类的实例,而不产生其他内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接