ジェネリック型から継承することをお勧めしますか?


35

List<string>型注釈またはStringListどこで使用するのが良いですか?StringList

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

私はに走った、いくつかこれらアイロニー


ジェネリック型から継承しない場合、なぜそこにあるのですか?
Mawg

7
@Mawgこれらは、インスタンス化にのみ使用できます。
テオドロスチャツィジアンナキス14

3
これは、コード内で参照するには、私の最も好きなものの一つである
KIK

4
うん いいえ、真剣に。間の意味の違いは何だStringListとはList<string>?何もありませんが、あなたは(一般的な「あなた」)あなたのプロジェクトだけに固有であり、コードを研究するまで他の人には何も意味しないさらに別の詳細をコードベースに追加することができました。文字列のリストを呼び出し続けるために、文字列のリストの名前をマスクするのはなぜですか?一方で、あなたがやりたいなら、BagOBagels : List<string>私はそれがより理にかなっていると思います。IEnumerableとして反復することができます。外部のコンシューマーは実装を気にしません。
クレイグ14

1
XAMLには、ジェネリックを使用できないという問題があり、リストを作成するためにこのような型を作成する必要がありました
ダニエルリトル

回答:


27

ジェネリック型から継承したい別の理由があります。

マイクロソフトでは、メソッドシグネチャでジェネリック型を入れ子にしないことお勧めします

ジェネリックの一部にビジネスドメインの名前付きタイプを作成することをお勧めします。の代わりに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は2つのコレクションのどちらが実際に必要かについて非常に具体的になります。これは、2つのコレクションを非常に簡単に解決できるIoCコンテナーで非常にうまく機能します。また、プログラマはより正確になります-ICollectionを使用することが理にかなっている場合MyConsumerClass、ジェネリックコンストラクタを使用できます。ただし、2つの派生型のいずれかを使用することがビジネス上意味をなさない場合、開発者はコンストラクターパラメーターを特定の型に制限できます。

要約すると、ジェネリック型から型を派生させることはしばしば価値があります-必要に応じてより明示的なコードを許可し、読みやすさと保守性を高めます。


12
これは絶対に馬鹿げたマイクロソフトの推奨事項です。
トーマスエディング14

7
パブリックAPIにとって、それはばかげているとは思わない。入れ子になったジェネリックは一見して理解するのが難しく、より意味のある型名は多くの場合読みやすくなります。
スティーブン

4
それはばかげているとは思いませんし、プライベートAPIには確かに大丈夫です... Microsoftでさえ、一般消費向けのAPIを作成する場合は、最も基本的な型を受け入れて返すことをお勧めします。
ポール・ダウス

35

原則として、ジェネリック型から継承することと他のものから継承することの間に違いはありません。

しかし、いったいなぜあなたはクラスから派生しそれ以上何も追加しないのでしょうか?


17
同意する。何も寄付せずに、「StringList」を読んで、「本当に何をするの?」と思いました。
ニール14

1
同様に、継承する言語では「extends」というキーワードを使用して継承します。これは、クラスを継承する場合は、さらに機能を拡張する必要があることを思い出してください。
エリオットブラックバーン14

14
-1、これは別の名前を選択して抽象化を作成するための1つの方法にすぎないことを理解していないためです。抽象化を作成することは、このクラスに何も追加しない非常に良い理由です。
Doc Brown 14

1
私の答えを考えてみてください、誰かがこれをすることにしたかもしれないいくつかの理由に行きます。
NPSF3000 14

1
「いったいなぜ、あなたはクラスから派生し、さらに何かを追加しないのですか?」私はユーザーコントロールのためにそれをやった。汎用ユーザーコントロールを作成できますが、Windowsフォームデザイナでは使用できません。そのため、タイプを指定して継承し、使用する必要があります。
ジョージT 14

13

私はC#についてあまりよく知りませんが、これがを実装する唯一の方法であると信じていtypedefます。

  • コードが山括弧のように見えないようにします。
  • 後でニーズが変わった場合に、基礎となるさまざまなコンテナを交換することができ、変更する権利を留保することが明確になります。
  • コンテキストに関連性​​の高い名前をタイプに付けることができます。

彼らは基本的に退屈で一般的な名前を選ぶことで最後の利点を捨てましたが、他の2つの理由はまだ残っています。


7
ええと…まったく同じではありません。C ++には、リテラルエイリアスがあります。StringList であるList<string> -彼らは互換的に使用することができます。これは、他のAPIを使用するとき(またはAPIを公開するとき)に重要ですList<string>。世界中の誰もがを使用しているためです。
テラスティン14

2
私はそれらがまったく同じものではないことを理解しています。利用可能な限り近い。弱いバージョンには確かに欠点があります。
カールビーレフェルト

24
いいえ、using StringList = List<string>;typedefに最も近いC#です。このようなサブクラス化は、引数を持つコンストラクターの使用を防ぎます。
ピートカーカム14

4
@PeteKirkham:StringListを定義して、usingそれを配置するソースコードファイルに制限しますusing。この観点から、継承はに近くなりtypedefます。また、サブクラスを作成しても、必要なすべてのコンストラクターを追加することはできません(退屈かもしれませんが)。
Doc Brown 14

2
@ Random832:これを別の方法で表現させてください:C ++では、typedefを使用して名前を1か所に割り当て、「単一の真実のソース」にすることができます。C#では、usingこの目的には使用できませんが、継承は使用できます。これは、私がピート・カーカムの賛成のコメントに同意しない理由を示しています-私はusingC ++の本当の代替とは考えていませんtypedef
Doc Brown 14

9

少なくとも1つの実用的な理由を考えることができます。

ジェネリック型から継承する非ジェネリック型を宣言すると(何も追加しなくても)、そうでなければ利用できない場所、または本来よりも複雑な方法で利用できる場所からそれらの型を参照できます。

そのようなケースの1つは、Visual Studioの設定エディターを使用して設定を追加する場合です。ここでは、非ジェネリック型のみが使用可能と思われます。そこに行けば、すべてに気付くでしょうSystem.Collectionsクラスが利用可能ですが、System.Collections.Generic該当するコレクションが表示されない。

別のケースは、WPFでXAMLを介して型をインスタンス化する場合です。2009より前のスキーマを使用する場合、XML構文で型引数を渡すことはサポートされていません。これは、2006年のスキーマが新しいWPFプロジェクトのデフォルトであると思われるという事実により悪化しています。


1
WCF Webサービスインターフェイスでは、この手法を使用して、見栄えの良いコレクションタイプ名(たとえば、ArrayOfPersonの代わりにPersonCollectionなど)を提供できます。
Rico Suter 14

7

歴史を調べる必要があると思います。

  • C#は、ジェネリック型を使用するよりもずっと長く使用されています。
  • 私は、与えられたソフトウェアが歴史のある時点で独自のStringListを持っていたことを期待しています。
  • これは、ArrayListをラップすることで実装されている可能性があります。
  • その後、誰かがリファクタリングしてコードベースを前進させます。
  • しかし、1001種類のファイルに触れることを望まなかったため、ジョブを終了します。

それ以外の場合、セオドロスチャツィジアンナキスが最良の答えを持っていました。たとえば、ジェネリック型を理解しないツールがまだたくさんあります。あるいは、彼の答えと歴史を混ぜたものかもしれません。


3
「C#は、ジェネリック型を使用するよりもずっと長く使用されています。」実際には、ジェネリックなしのC#は約4年間(2002年1月-2005年11月)存在していましたが、ジェネリックありのC#は9歳になりました。
svick 14


4

場合によっては、ジェネリック型を使用できないことがあります。たとえば、XAMLでジェネリック型を参照することはできません。これらの場合、最初に使用したいジェネリック型から継承する非ジェネリック型を作成できます。

可能であれば、私はそれを避けるでしょう。

typedefとは異なり、新しい型を作成します。StringListをメソッドへの入力パラメーターとして使用する場合、呼び出し元はを渡すことができませんList<string>

また、使用できないすべてのコンストラクターを明示的にコピーする必要があります。StringListの例ではnew StringList(new[]{"a", "b", "c"})List<T>このようなコンストラクターを定義していても、言えません。

編集:

受け入れられた答えは、ドメインモデル内で派生型を使用する場合の長所に焦点を当てています。この場合、それらが潜在的に役立つ可能性があることに同意しますが、それでも個人的にはそれらを避けます。

しかし、呼び出し側の生活を難しくしたり、パフォーマンスを無駄にしたりするので、(私の意見では)パブリックAPIでそれらを使用しないでください(暗黙的な変換は一般的にあまり好きではありません)。


3
XAMLでジェネリック型を参照することはできません」と言いますが、何を参照しているかわかりません。参照XAMLでのジェネリック
pswg

1
これは一例です。はい、2009 XAMLスキーマで可能ですが、VSのデフォルトである2006スキーマではできません。
ルーカスリーガー14

1
3番目の段落は半分だけ真実です。暗黙のコンバーターで実際にこれを許可できます;)
Knerd

1
@ Knerd、true、しかしその後、「暗黙的に」新しいオブジェクトを作成します。さらに多くの作業を行わない限り、データのコピーも作成されます。おそらく小さなリストには関係ありませんが、誰かが数千の要素を含むリストを渡した場合は、楽しんでください=)
Lukas Rieger 14

@LukasRiegerあなたは正しいです:PIはそれを指摘したかっただけです:P
Knerd 14

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

誰かが私の頭の上からそれをしようとする理由は3つ考えられます。

1)宣言を簡素化するには:

確かStringListとは、ListStringほぼ同じ長さであるが、代わりにあなたが作業していると想像UserCollection実際だことDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>や他のいくつかの大規模な一般的な例。

2)AOTを支援するには:

Just In Timeコンパイルではなく、Ahead Of Timeコンパイルを使用する必要がある場合があります。たとえば、iOS向けの開発です。AOTコンパイラは、すべてのジェネリック型を事前に把握することが困難な場合があり、これはヒントを提供しようとする試みである可能性があります。

3)機能を追加または依存関係を削除するには:

実装したい特定の機能StringList(拡張メソッドで非表示、まだ追加されていない、または履歴)があり、List<string>継承せずに追加できないか、汚染List<string>したくない場合があります。

あるいは、彼らはStringListトラックの実装を変更したいかもしれないので、今のところクラスをマーカーとして使用しているだけです(通常、このインターフェイスを作成/使用しますIList)。


また、Ironyでこのコードを発見したことにも注意してください。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
宣言を単純化する(または何でも単純化する)ことで、複雑さが軽減されるのではなく、複雑さが増すことがあります。言語をある程度熟知している人List<T>は誰もが何であるかをよく知っていますが、StringList実際にそれが何であるかを理解するために文脈を調べなければなりません。現実には、それはただList<string>、別の名前で行っています。このような単純なケースでこれを行うことには、セマンティック上の利点はないようです。よく知られている型を、別の名前の同じ型に置き換えるだけです。
クレイグ14

@Craig私は、ユースケース(より長い宣言)と他の考えられる理由の説明で述べたように同意します。
NPSF3000 14

-1

それは良い習慣ではないと思います。

List<string>「文字列の一般的なリスト」を伝えます。明らかに List<string>拡張メソッドが適用されます。理解するために必要な複雑さは少なくなります。リストのみが必要です。

StringList「別個のクラスの必要性」を伝えます。たぶん、 List<string>拡張メソッドは、(このため実際の型の実装を確認してください)適用されます。コードを理解するには、さらに複雑さが必要です。リストと派生クラスの実装に関する知識が必要です。


4
とにかく拡張メソッドが適用されます。
テオドロスチャツィギアンナキス14

2
もちろん。しかし、StringListを調べて、それがから派生するのを見るまで、あなたは知りませんList<string>
ルードジャ14

@TheodorosChatzigiannakis「拡張方法は適用されない」という意味で、Ruudjahが詳しく説明できたのだろうか。拡張メソッドは、どのクラスで可能です。確かに、.NETフレームワークライブラリには、汎用コレクションクラスに適用されるLINQと呼ばれる多くの拡張メソッドが付属していますが、それは拡張メソッドが他のクラスに適用されないという意味ではありませんか?おそらく、Ruudjahは一見して明らかではない点を指摘しているのでしょうか?;-)
クレイグ14

「拡張方法は適用されません。」これはどこで読みますか?私は、拡張メソッドは、」言っていることが適用されます。
Ruudjah

1
ただし、型の実装では、拡張メソッドが適用されるかどうかはわかりませんか?クラスであるという事実だけが、拡張メソッド適用されることを意味ます。あなたのタイプのコンシューマーは、コードとは完全に独立して拡張メソッドを作成できます。それが私が得ている、または求めていることだと思います。LINQについて話しているだけですか?LINQは、拡張メソッドを使用して実装されます。しかし、特定の型のLINQ固有の拡張メソッドが存在しないということは、その型の拡張メソッドが存在しない、または存在しないという意味ではありません。誰でもいつでも作成できます。
クレイグ14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.