从泛型类型继承是一种好习惯吗?


35

最好List<string>在类型注释中使用还是StringListStringList

class StringList : List<String> { /* no further code!*/ }

我碰到了几个这些中的反讽


如果您不继承泛型类型,那么为什么它们在那里?
Mawg 2014年

7
@Mawg它们仅可用于实例化。
Theodoros Chatzigiannakis 2014年

3
这是我在代码中最不喜欢看到的东西之一
Kik 2014年

4
uck 不,认真 StringList和之间的语义差异是List<string>什么?没有,但是您(通用的“您”)设法在代码库中添加了仅对您的项目唯一的另一个细节,对其他人而言,直到他们研究了代码之后,这才意味着什么。为什么屏蔽字符串列表的名称只是为了继续称其为字符串列表?另一方面,如果您想这样做,BagOBagels : List<string>我认为这更有意义。您可以将其作为IEnumerable进行迭代,外部使用者不关心实现-全方位。
克雷格

1
XAML存在一个问题,即您不能使用泛型,而您需要创建这样的类型来填充列表
Daniel Little

Answers:


27

还有一个原因可能导致您要从泛型类型继承。

Microsoft建议避免在方法签名中嵌套通用类型

那么,为某些泛型创建业务域命名类型是一个好主意。而不是IEnumerable<IDictionary<string, MyClass>>创建一个类型MyDictionary : IDictionary<string, MyClass>,然后使您的成员成为一个IEnumerable<MyDictionary>,而不是让它更具可读性。它还允许您在许多地方使用MyDictionary,从而增加代码的显式性。

这听起来似乎不是一个巨大的好处,但是在现实世界的业务代码中,我已经看到了使您的头发站得住脚的类属。之类的事情List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>。该泛型的含义绝非显而易见。但是,如果使用一系列显式创建的类型来构造它,则原始开发人员的意图可能会更加清晰。更进一步,如果由于某种原因需要更改该泛型类型,则与在派生类中进行更改相比,找到并替换该泛型类型的所有实例可能是一个真正的痛苦。

最后,还有另一个原因导致人们可能希望创建自己的派生自泛型的类型。考虑以下类别:

public class MyClass
{
    public ICollection<MyItems> MyItems { get; private set; }
    // plumbing code here
}

public class MyOtherClass
{
    public ICollection<MyItems> MyItemCache { get; private set; }
    // plumbing code here
}

public class MyConsumerClass
{
    public MyConsumerClass(ICollection<MyItems> myItems)
    {
        // use the collection
    }
}

现在我们如何知道ICollection<MyItems>要传递给MyConsumerClass的构造函数?如果我们创建派生类class MyItems : ICollection<MyItems>class MyItemCache : ICollection<MyItems>,则MyConsumerClass可以非常明确地确定其实际需要两个集合中的哪个集合。然后,这对于IoC容器非常有效,它可以非常轻松地解析两个集合。它还允许程序员更加准确-如果可以MyConsumerClass与任何ICollection一起使用,则可以采用通用构造函数。但是,如果使用两种派生类型之一从商业上讲没有意义,则开发人员可以将构造函数参数限制为特定类型。

总之,从泛型类型派生类型通常是值得的-它允许在适当的情况下使用更明确的代码,并有助于提高可读性和可维护性。


12
这是微软绝对荒谬的建议。
Thomas Eding 2014年

7
我不认为公共API这么荒唐。嵌套的泛型一目了然,而且更有意义的类型名称通常可以提高可读性。
斯蒂芬

4
我不认为这很荒谬,对于私有API来说当然还可以...但是对于公共API,我认为这不是一个好主意。甚至Microsoft在编写供公众使用的API时也建议接受并返回基本类型。
Paul d'Aoust 2015年

35

原则上,从泛型类型继承与从任何其他类型继承之间没有区别。

但是,为什么在地球上您会从任何类别派生而又不添加任何东西呢?


17
我同意。在不做任何贡献的情况下,我会读“ StringList”,然后对自己说:“它实际上是做什么的?”
尼尔

1
我也同意,可以用关键词“ extends”继承您的语言,这很好地提醒您,如果您要继承一个类,则应该使用进一步的功能对其进行扩展。
Elliot Blackburn

14
-1,因为不了解这只是通过选择其他名称来创建抽象的一种方法-创建抽象可能是不向此类添加任何内容的很好理由。
布朗

1
考虑我的回答,我探讨了某些人可能会决定这样做的原因。
NPSF3000

1
“为什么你会从任何阶层派生而又不增加任何东西呢?” 我已经完成了用户控件。您可以创建一个通用的用户控件,但不能在Windows窗体设计器中使用它,因此必须从它继承,指定类型并使用该控件。
乔治T

13

我不太了解C#,但是我相信这是实现的唯一方法typedef,C ++程序员通常出于以下原因使用它:

  • 它可以防止代码看起来像尖括号。
  • 如果以后需要更改,它可以让您换出不同的基础容器,并明确表示您保留这样做的权利。
  • 它使您可以为类型指定与上下文更相关的名称。

他们基本上通过选择无聊的通用名称而放弃了最后一个优势,但其他两个原因仍然存在。


7
嗯...他们不太一样。在C ++中,您有一个文字别名。StringList 一个List<string>-他们可以互换使用。在使用其他API(或公开API)时,这一点很重要,因为世界上其他所有人都使用List<string>
Telastyn 2014年

2
我意识到他们不是一回事。尽可能地接近。较弱的版本肯定有缺点。
Karl Bielefeldt 2014年

24
不,using StringList = List<string>;是与typedef最接近的C#等效项。这样的子类化将防止使用带参数的任何构造函数。
Pete Kirkham 2014年

4
@PeteKirkham:通过将StringList using限制using为放置StringList 的源代码文件。从这个角度来看,继承更接近typedef。创建子类并不能阻止它添加所需的所有构造函数(尽管可能很乏味)。
布朗

2
@ Random832:让我用不同的方式表达这一点:在C ++中,可以使用typedef 在一个地方分配名称,以使其成为“单一事实来源”。在C#中,using不能用于此目的,但是继承可以。这说明了为什么我不同意Pete Kirkham提出的意见-我不认为using这是C ++的真正替代方案typedef
布朗

9

我可以想到至少一个实际的原因。

声明从泛型类型继承的非泛型类型(即使不添加任何内容),都可以从原本不可用的地方引用这些类型,或者以比应有的方式更复杂的方式来引用这些类型。

一种这样的情况是通过Visual Studio中的“设置”编辑器添加设置时,其中似乎只有非通用类型可用。如果您去那里,您会注意到所有System.Collections类都可用,但是没有任何System.Collections.Generic适用的集合出现。

另一种情况是在WPF中通过XAML实例化类型。使用2009之前的架构时,不支持在XML语法中传递类型参数。由于2006模式似乎是新的WPF项目的默认模式,因此情况变得更糟。


1
在WCF Web服务接口中,您可以使用此技术来提供外观更好的集合类型名称(例如,PersonCollection而不是ArrayOfPerson或其他名称)
Rico Suter

7

我认为您需要研究一些历史

  • C#的使用时间比其通用类型的使用时间长得多。
  • 我希望给定的软件在历史上可以拥有自己的StringList。
  • 这很可能通过包装ArrayList来实现。
  • 然后有人要重构以向前移动代码库。
  • 但是不希望碰到1001个不同的文件,所以完成工作。

否则,Theodoros Chatzigiannakis会有最好的答案,例如,仍然有许多不了解泛型类型的工具。或者甚至是他的回答和历史的混合。


3
“使用C#的时间比使用通用类型的时间长得多。” 并非如此,没有泛型的C#存在大约4年(2002年1月至2005年11月),而带有泛型的C#已有9年历史了。
svick 2014年


4

在某些情况下,不可能使用通用类型,例如,您不能在XAML中引用通用类型。在这些情况下,您可以创建一个非泛型类型,该非泛型类型继承自最初要使用的泛型类型。

如果可能的话,我会避免的。

与typedef不同,您可以创建一个新类型。如果将StringList用作方法的输入参数,则调用者无法传递List<string>

同样,您将需要显式复制希望使用的所有构造函数,在StringList示例中new StringList(new[]{"a", "b", "c"}),即使List<T>定义了这样的构造函数,您也不会说。

编辑:

在域模型内使用派生类型时,可接受的答案侧重于专业人士。我同意它们在这种情况下可能很有用,不过,即使在那儿,我个人也避免使用它们。

但是(我认为)永远不要在公共API中使用它们,因为这会使调用者的生活更加困难或浪费性能(总体上我也不太喜欢隐式转换)。


3
您说:“ 您不能在XAML中引用泛型类型 ”,但是我不确定您要指的是什么。参见XAML中的泛型
pswg 2014年

1
这只是一个例子。是的,使用2009 XAML架构是可能的,但是AFAIK不能用于2006架构,这仍然是VS的默认设置。
Lukas Rieger 2014年

1
您的第三段是正确的一半,您可以实际使用隐式转换器;)
Knerd 2014年

1
@Knerd,是的,但是然后您“隐式”创建了一个新对象。除非您做很多工作,否则这还将创建数据的副本。小名单可能无关紧要,但如果有人传递包含几千个元素的名单,那就可以玩得开心=)
Lukas Rieger 2014年

@LukasRieger你是对的:PI只想指出一点:P
Knerd 2014年

2

值得一提的是,这是一个示例,说明了如何在Microsoft的Roslyn编译器中使用从通用类继承而无需更改类名的情况。(对此我感到迷惑不解,以至于最终在这里进行搜索以查看是否确实可行。)

在项目CodeAnalysis中,您可以找到以下定义:

/// <summary>
/// Common base class for C# and VB PE module builder.
/// </summary>
internal abstract class PEModuleBuilder<TCompilation, TSourceModuleSymbol, TAssemblySymbol, TTypeSymbol, TNamedTypeSymbol, TMethodSymbol, TSyntaxNode, TEmbeddedTypesManager, TModuleCompilationState> : CommonPEModuleBuilder, ITokenDeferral
    where TCompilation : Compilation
    where TSourceModuleSymbol : class, IModuleSymbol
    where TAssemblySymbol : class, IAssemblySymbol
    where TTypeSymbol : class
    where TNamedTypeSymbol : class, TTypeSymbol, Cci.INamespaceTypeDefinition
    where TMethodSymbol : class, Cci.IMethodDefinition
    where TSyntaxNode : SyntaxNode
    where TEmbeddedTypesManager : CommonEmbeddedTypesManager
    where TModuleCompilationState : ModuleCompilationState<TNamedTypeSymbol, TMethodSymbol>
{
  ...
}

然后在项目CSharpCodeanalysis中有以下定义:

internal abstract class PEModuleBuilder : PEModuleBuilder<CSharpCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState>
{
  ...
}

这个非泛型的PEModuleBuilder类在CSharpCodeanalysis项目中得到了广泛的使用,该项目中的几个类直接或间接地继承自该类。

然后在项目BasicCodeanalysis中有以下定义:

Partial Friend MustInherit Class PEModuleBuilder
    Inherits PEModuleBuilder(Of VisualBasicCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState)

由于我们可以(希望地)假设Roslyn是由具有丰富C#知识的人编写的,以及如何使用它,所以我认为这是该技术的推荐。


是。而且它们也可以在声明时直接用于优化where子句。
哨兵

1

某人试图做到这一点的三个可能原因是:

1)为了简化声明:

当然StringListListString它们的长度大约相同,但可以想象一下,您正在使用的UserCollection是实际的Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>或其他大型通用示例。

2)要帮助AOT:

有时您需要使用Ahead of Time编译而不是Just In Time Compilation-例如针对iOS开发。有时AOT编译器很难提前弄清所有泛型类型,这可能是尝试为它提供提示。

3)要添加功能或删除依赖性:

也许他们具有要实现的特定功能StringList(可能隐藏在扩展方法中,尚未添加,或者是历史功能),或者无法List<string>继承而不能添加到这些功能,或者他们不想污染List<string>带。

或者,他们可能希望改变现有的实现方式StringList,因此,现在仅使用该类作为标记(通常,例如,您将为此使用/创建接口IList)。


我还要指出,您已经在Irony中找到了此代码,它是一种语言解析器-一种相当不常见的应用程序。使用此消息可能还有其他一些特定原因。

这些示例表明,编写这样的声明可能有正当的理由-即使它不太常见。与任何事物一样,请考虑几个选项,然后选择最适合您的时间。


如果您查看源代码,那么选项3就是原因-他们使用此技术来帮助添加功能/专业化集合:

public class StringList : List<string> {
    public StringList() { }
    public StringList(params string[] args) {
      AddRange(args);
    }
    public override string ToString() {
      return ToString(" ");
    }
    public string ToString(string separator) {
      return Strings.JoinStrings(separator, this);
    }
    //Used in sorting suffixes and prefixes; longer strings must come first in sort order
    public static int LongerFirst(string x, string y) {
      try {//in case any of them is null
        if (x.Length > y.Length) return -1;
      } catch { }
      if (x == y) return 0;
      return 1; 
    }

1
有时寻求简化声明(或简化任何操作)可能会导致复杂性增加,而不是降低复杂性。一定程度地了解该语言的每个人都知道a List<T>是什么,但是他们必须StringList在上下文中进行检查才能真正理解它的含义。实际上,只是List<string>用不同的名称命名。在这种简单的情况下,这样做似乎没有任何语义上的优势。您实际上只是用一个具有不同名称的相同类型替换了一个知名类型。
克雷格2014年

@Craig我同意,正如我对用例的解释(较长的声明)和其他可能的原因所指出的那样。
NPSF3000

-1

我会说这不是好习惯。

List<string>传达“字符串的通用列表”。显然, List<string>扩展方法适用。需要了解的复杂程度较低;只需要列表。

StringList传达“需要一个单独的班级”。也许 List<string>扩展方法适用(为此检查实际的类型实现)。理解代码需要更多的复杂性。列表和派生的类实现知识是必需的。


4
扩展方法仍然适用。
Theodoros Chatzigiannakis 2014年

2
当然。但是您不知道,直到您检查StringList并查看它源自List<string>
Ruudjah 2014年

@TheodorosChatzigiannakis我想知道Ruudjah是否可以通过“扩展方法不适用”来阐述他的意思。扩展方法对于任何类都是可行的。当然,.NET框架库附带了许多扩展方法,这些扩展方法被称为LINQ,适用于通用集合类,但这并不意味着扩展方法不适用于任何其他类?也许Ruudjah提出的观点乍一看并不明显?;-)
Craig

“扩展方法不适用。” 你在哪里读的?我说的“扩展方法可以适用。
Ruudjah

1
类型实现不会告诉您扩展方法是否适用,对吗?只是它是一个类这一事实意味着扩展方法确实适用。任何类型的使用者都可以完全独立于代码创建扩展方法。我想这就是我要问或问的。您是在谈论LINQ吗?LINQ是使用扩展方法实现的。但是,缺少针对特定类型的LINQ特定的扩展方法,并不意味着该类型的扩展方法不存在或不存在。它们可以由任何人随时创建。
Craig 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.