为什么要派生一个List<T>类来重新定义索引器?

4

我经常看到类似这样的列表派生类:

class MyClassList : List<MyClass>
{
    public MyClass this[int index]
    {
        get { return (MyClass)base[index]; }
    }
}

这个继承有什么意义呢?看起来好像只是重新陈述了成员的类型转换。我可以理解其他类型的索引器,但这只是默认的List索引器的重述,并引发了Visual Studio警告:隐藏基本索引器。这是正确或错误的做法,为什么呢?

3
我不知道你从哪里找到这样的代码,但它有数百万个错误!请问需要翻译什么其他内容吗? - leppie
2
你在哪里看到的?因为我从来没有见过。这是一段可怕的代码。 - flq
4
猜测:新手程序员试图隐藏setter。(哈哈) - leppie
1
@flq 哎呀,这可能是我在过去几周里查找的无数教程、代码示例和如何做某事的指南之一,因为我正在加强我的 C# 技能。请放心,如果我找到另一个,我会发布链接,以便 SO 的全部力量可以释放给那个不幸的罪犯。 - downwitch
7个回答

5
也许这是一种非常不好的尝试,通过索引器来防止值被覆盖?
MyClassList x = new MyClassList();
x.Add(new MyClass());
x[0] = new MyClass(); // Error!

当然,这并没有阻止这种情况发生:
List<MyClass> x = new MyClassList();
x.Add(new MyClass());
x[0] = new MyClass(); // No problem here...

基本上,这是个不好的想法。不幸的是,糟糕的代码充斥着整个IT领域 - 不要仅仅因为它存在而推断出其有用性 :(


2

这样做没有什么好处。它隐藏了基本索引器而不是覆盖它,这可能会很危险,而且根本没有任何效果。

在大多数情况下,最好直接使用List<MyClass>。如果您没有计划扩展List<>的功能,那么没有必要为此创建特殊的类。


好的,接着上面的话题——如果要扩展基类,我认为基类索引器应该保持不变,对吗? - downwitch
@downwitch:是的,除非有特殊原因需要这样做。例如,您可能希望在集合更改时随时触发事件。在这种情况下,您将覆盖(而不是隐藏)基础索引器,导致它调用基础索引器,然后触发相应的事件。或者您可能决定在设置了某个标志后,不允许进行任何更改:在这种情况下,您将覆盖基础索引器,如果设置了该标志,则抛出异常。有合法的理由可以重写索引器。隐藏它的原因要少得多。在这种情况下,两者都不适用。 - StriplingWarrior

1

我认为它应该隐藏基类的set访问器,使其看起来像索引器是只读的。但这是无用的,因为很容易绕过它:

MyClassList list = ...

((List<MyClass>)list)[index] = value;

无论如何,List<T> 类并不适用于继承。如果您需要创建一个专门的集合,请从 Collection<T> 继承。


我觉得你最后的陈述有点令人困惑。难道多态不是基于“任何”类都设计为可继承的思想吗?在我看来,List<T>的内置方法数量和相对容易的重用性使它成为更快速的基类。我错过了什么吗? - downwitch
你所忽略的是 List<T> 类中的所有方法都不是虚方法,这使得无法对它们进行重写。你可以使用 隐藏 的方式,但这是完全不同的概念... 隐藏基类定义的方法将不参与多态性。 - Thomas Levesque
确实,这非常有道理。尽管我已经阅读了很多关于这个主题的内容,但我记不起有人费心陈述这个简单的原则:如果一个类被设计用于继承,那么它的方法就是虚拟的。这可能是显而易见的,但对我来说并非如此。 - downwitch

1

我不认为这样做有任何用处。 此外,强制类型转换是不必要的。


0

我以前见过这种情况,人们想将它序列化为另一种类型。如果现在那是唯一的代码,并且没有其他原因需要它成为 MyClassList 类型,那么这完全没有意义。


0

这基本上是一个尝试,以正确覆盖[]访问List,实现一些自定义元素访问逻辑。

值得注意的是,提供的代码不好,如果不是危险的。如果您想对列表进行一些棘手的操作,请不要覆盖(或倾向于这样做)[],而是为此实现一些自定义方法。


0

我猜代码试图表现为只读的 List。不能通过索引向类型为 MyClassList <T> 的变量的项写入,但是可以将其转换回 List <T> 并以这种方式编写变量。有时,持有具有受限能力的类型的变量,从而使其具有实际能力远大于此的对象是有意义的。不过,正确的方法通常是使用接口,其中一个主要示例是 IEnumerable <T>。如果将 List <T> 传递给接受类型参数为 IEnumerable <T> 的程序,则该程序可以将其参数强制转换回 List <T> 并使用像 Add()Remove() 等成员,但大多数接受类型参数为 IEnumerable <T> 的程序不会尝试将其用作其他任何东西。

代码示例中的一个主要问题是,更“强大”的方向是基类,而不是派生类型。因为 List<T> 派生自 IEnumerable<T>,这意味着所有 List<T> 的实例都可以枚举,但并非所有可枚举的东西都具有 List<T> 中的额外功能。相比之下,根据您的类的实现,每个 MyClassList<T> 都可以读取和写入,但只有一些 List<T> 的实例可以用作 MyClassList<T>

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