web-dev-qa-db-ja.com

ジェネリック型から継承することは良い習慣ですか?

List<string>型注釈またはStringList where StringList

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

sironal of these in Irony に遭遇しました。

35
Ruudjah

ジェネリック型から継承する必要がある理由はもう1つあります。

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
    }
}

ここで、MyConsumerClassのコンストラクターに渡すICollection<MyItems>をどのようにして知るのでしょうか。派生クラスclass MyItems : ICollection<MyItems>およびclass MyItemCache : ICollection<MyItems>を作成すると、MyConsumerClassは、2つのコレクションのどちらが実際に必要かを非常に具体的に指定できます。これは、IoCコンテナで非常にうまく機能し、2つのコレクションを非常に簡単に解決できます。また、プログラマーをより正確にすることができます。MyConsumerClassが任意のICollectionで機能することが理にかなっている場合は、ジェネリックコンストラクターを使用できます。ただし、2つの派生型のいずれかを使用することがビジネス上意味をなさない場合、開発者はコンストラクターパラメーターを特定の型に制限できます。

要約すると、ジェネリック型から型を派生させることはしばしば価値があります-適切な場合、より明示的なコードを可能にし、可読性と保守性を助けます。

27
Stephen

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

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

35
Julia Hayward

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

  • コードが山括弧の海のように見えないようにします。
  • これにより、後でニーズが変更された場合に、基盤となるさまざまなコンテナーを交換することができ、そうする権利を予約することが明確になります。
  • これにより、型をコンテキストに関連する名前にすることができます。

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

13
Karl Bielefeldt

少なくとも1つの実用的な理由が考えられます。

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

そのようなケースの1つは、Visual Studioの設定エディターを介して設定を追加する場合です。この場合、ジェネリック以外のタイプしか使用できないようです。そこに行くと、すべてのSystem.Collectionsクラスを使用できますが、System.Collections.Genericからのコレクションは適切に表示されません。

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

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

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

そうでなければ、Theodoros Chatzigiannakisが最高の答えを出しました。ジェネリック型を理解しないツールはまだたくさんあります。それとも彼の答えと歴史の混合かもしれません。

7
Ian

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

できれば避けま​​す。

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

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

編集:

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

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

4
Lukas Rieger

価値のあるものとして、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#の広範な知識を持つ人々によって記述されたものであり、C#の使用方法を想定しているため、これはこの手法の推奨事項だと思います。

2
RenniePet

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

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

確かにStringListListStringはほぼ同じ長さですが、実際にはDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>であるUserCollectionまたは他のいくつかの大きな一般的な例を使用していると想像してください。

2)AOTを支援するには:

Just In Timeコンパイルではなく、Ahead Of Timeコンパイルを使用する必要がある場合があります。 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
NPSF3000