在C#中是否可能为泛型制作“这种类型”?

7

这是一个理论问题,如果你不想了解理论可以跳过。

假设你有两个类,其中一个类继承自另一个类。基类是通用的,并且具有在封闭类型中必须返回该封闭类型某个实例的方法。

就像这样(注意文本中的???):

public class Adapter<T>
{
 public virtual ??? DoSomething()
 {
  ...
 }
}

public class AdaptedString : Adapter<String>
{
 public override AdaptedString DoSomething()
 {
  ...
 }
}

我无法做到这一点,因为没有办法引用将从泛型类型派生的闭合类型。 (对不起,语言有些生硬,不知道该如何表达。)没有关键字可以替换 ??? 来指定此方法将返回从此泛型类型派生的类型的实例。

相反,我可以使用一个解决方法,即显式传递类型名称到泛型基类中。但这看起来有些冗余。

public class Adapter<TThis,T>
{
 public virtual TThis DoSomething()
 {
  ...
 }
}

public class AdaptedString : Adapter<AdaptedString,String>
{
 public override AdaptedString DoSomething()
 {
  ...
 }
}

如果在基类中需要访问TThis实例的成员,我必须添加一个约束。这次看起来有点丑 - 请注意约束:

public class Adapter<TThis,T>
 where TThis : Adapter<TThis, T>
{
 protected int _field; 

 ...

 public bool Compare( TThis obj )
 {
  return _field == obj._field;
 }
}

public class AdaptedString : Adapter<AdaptedString,String>
{
 ...
}

是的,所有内容都在运作,但如果我可以在第一个代码片段中使用一些关键字而不是 ???,那么它会看起来更好。例如 "thistype"。

你认为这样做是否可行?它有用吗?还是只是很愚蠢?

4个回答

15

没有什么能使这个模式更加简单,实际上这个模式本身也不是百分之百可靠的,因为可能会出现以下情况:

class TypeA : Adapter<TypeA, string>

class TypeB : Adapter<TypeA, string> // Bug!

这里的第二行是完全合法的 - TypeATThis类型参数的有效类型参数,即使它不是我们想要的。基本上,类型系统不允许我们表达“T必须是此类型”的概念。
然而,我不同意那些认为这是一个糟糕或无用的模式的人。在Protocol Buffers中,我发现它很有用(尽管复杂)- 如果没有它,该协议将糟糕得多。例如:
Foo foo = new Foo.Builder { Name="Jon" }.Build();

如果Foo.Build()没有强类型返回Foo,即使Build方法在IBuilder<...>中被指定,它也不能正常工作。

如果可以轻松避免这种情况,那么最好避免,因为它变得非常复杂 - 但我认为这是一个有用的模式。


Jon,你示例中的第二行只是有些不同,不符合这个模式。如果有必要使用它,在现有语法下也能实现。我认为这种语法使得我的示例更加复杂。在发现我们代码中的bug后,我写了这篇文章。我最初写这个基类已经有一段时间了,但我不得不重新阅读几次才能再次掌握。难怪那个人会引入一个bug。 - XOR
1
@XOR:我的观点是第二行代码是合法的,但在这种情况下不可取。我会编辑答案以使其更清晰明了。 - Jon Skeet

5
在这种情况下,您通常只需要引用基类:
public class Adapter<T> { 
   public virtual Adapter<T> DoSomething();

试图实现你所完成的操作违反了Liskov替换原则


1
看起来这里是完美的答案:https://dev59.com/GknSa4cB1Zd3GeqPMkcs - dtb
是的,但这不是同样的情况。他们并不是从一个基类工作,而是从一个具体类型开始的。 - Reed Copsey
不,这样做行不通,因为我想让派生(封闭)类型中的DoSomething返回AdapdedString,而不是它的基础类型。 - XOR
我不同意它违反了 Liskov 原则。我们的基类是开放的。无论如何,我们都不能针对它编写代码,因此派生类型根本没有任何可以破坏的东西。 - XOR
异或(XOR):如果是这样,那么这个方法可以正常工作。只是不要将它添加到基类中。具体的类可以有这个方法。但是,如果您将其添加到基类中,则会违反LSP原则。 - Reed Copsey

-2

我也在寻找一个有争议的使用案例,尽管这是一个有趣的想法。

你是想改变如何约束可以使用的泛型类型吗?听起来你想要假设一些基本功能而不知道实际类型;这就是接口的作用。Where从句对于这些问题非常方便。

class Dictionary<K, V>
where K : IComparable, IEnumerable
where V : IMyInterface
{
    public void Add(K key, V val)
    {
    }
}

以上示例限制了K(键)必须是可比较和可枚举的,而V必须通过您自己的接口实现所需的任何客户功能。

-2

如果你的派生类中的继承方法需要返回派生类型而不是基础类型(称为 协变返回类型),这在C#中已经得到支持。


不行,因为C#不支持协变返回类型。 - Jon Skeet
即使这样,那仍然不足以总能提供帮助。 - Jon Skeet

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