web-dev-qa-db-ja.com

静的ジェネリッククラスに使用しますか?

C#でのStaticジェネリッククラスの主な用途は何ですか?それらはいつ使用されるべきですか?それらの使用法を最もよく表す例は何ですか?

例えば.

public static class Example<T>
{
   public static ...
}

それらで拡張メソッドを定義することはできないので、それらはそれらの有用性においていくらか制限されているように見えます。このトピックに関するWeb参照はほとんどないため、それらを使用している人は明らかに多くありません。ここにいくつかあります:-

http://ayende.com/Blog/archive/2005/10/05/StaticGenericClass.aspx

辞書としての静的ジェネリッククラス


与えられた回答の要約

重要な問題は、「静的メソッドを持つ静的ジェネリッククラスと静的ジェネリックメンバーを持つ非ジェネリック静的クラスの違いは何ですか?」

どちらを使用するかについての決定は、「クラスはタイプ固有の状態を内部に格納する必要がありますか?」を中心に展開しているようです。

型固有の内部ストレージが必要ない場合は、呼び出し構文が優れており、その中で拡張メソッドを定義できるため、ジェネリック静的メソッドを持つ静的非ジェネリッククラスが望ましいようです。

26
Ian Mercer

リフレクションの多いコードをキャッシュするために静的ジェネリッククラスを使用します。

オブジェクトをインスタンス化する式ツリーを作成する必要があるとしましょう。クラスの静的コンストラクターで一度ビルドし、ラムダ式にコンパイルしてから、静的クラスのメンバーにキャッシュします。私はこれらのクラスを公に評価可能にしないことがよくあります-それらは通常他のクラスのヘルパーです。この方法で式をキャッシュすることにより、ある種の_Dictionary<Type, delegate>_に式をキャッシュする必要がなくなります。

[〜#〜] bcl [〜#〜] にこのパターンの例があります。 (DataRow)拡張メソッドField<T>()およびSetField<T>()は、(プライベート)静的ジェネリッククラス_System.Data.DataRowExtensions+UnboxT<T>_を使用します。 Reflectorでチェックしてください。

24
Greg

クラスをstaticにすると、機能は追加されません。クラスをインスタンス化せずに使用する場合は、これは便利なチェックです。そして、そのためのいくつかの用途があります...

静的ジェネリッククラスを使用して、制限を回避できます。C#では部分的な特殊化は許可されていません。つまり、すべてのタイプパラメータを指定するか、指定しない必要があります。ただし、それは不必要に冗長になる可能性があります。

例えば:

_static class Converter {
    public TOut Convert<TIn, TOut>(TIn x) {...}
}
_

前のクラスでは、戻り値に対して推論が機能しないため、型推論は許可されていません。ただし、部分的に特殊化することはできないため、入力タイプも指定せずに戻り値タイプを指定することもできません。 (おそらく静的な)ジェネリッククラスを使用して、次の2つのタイプのうちの1つのみを指定できます。

_static class ConvertTo<TOut> {
    public TOut Convert<TIn>(TIn x) {...}
}
_

そうすれば、型推論をパラメーター型で機能させ、戻り値の型のみを指定できます。

(上記の場合も考えられますが、もちろん、ジェネリッククラスが静的である必要はありません)。


次に、(Steven 最初に指摘された )構築された型ごとに個別の静的フィールドが存在するため、静的クラスは型または型の組み合わせに関する追加情報を格納するのに最適な場所になります。本質的に、それは型をキー入力する半静的ハッシュテーブルです。

型をキー入力する半静的ルックアップテーブルは少し難解に聞こえますが、実際には非常に便利な構造です。これにより、高価なリフレクションとコード生成の結果をほぼ自由に検索できる場所に保存できます(辞書よりも安価です)。これは、JITされ、.GetType())の呼び出しを回避するためです。メタプログラミングをしているなら、これは素晴らしいことです!

たとえば、これを ValueUtils で使用して、生成されたハッシュ関数を格納します。

_//Hash any object:
FieldwiseHasher.Hash(myCustomStructOrClass);

//implementation:
public static class FieldwiseHasher {
    public static int Hash<T>(T val) { return FieldwiseHasher<T>.Instance(val); }
}

public static class FieldwiseHasher<T> {
    public static readonly Func<T, int> Instance = CreateLambda().Compile();
    //...
}
_

静的ジェネリックメソッドを使用すると、type-in​​ferenceを使用して非常に簡単に使用できます。ジェネリッククラスの静的フィールドにより、実質的にオーバーヘッドがなくなります(メタ)データのストレージ。 ORMが DapperPetaPoco のように、このような手法を使用していても、私はまったく驚かないでしょう。ただし、(デ)シリアライザーにも最適です。制限は、compile-time型にバインドしているため、オーバーヘッドが低くなることです。渡されたオブジェクトが実際にサブクラスのインスタンスである場合は、おそらく間違ったタイプにバインドしている可能性があります。そのような種類を回避するためのチェックを追加すると、オーバーヘッドが少ないという利点が損なわれます。

14
Eamon Nerbonne

ジェネリック型の静的フィールドは、実際の型Tに固有です。これは、型固有のキャッシュを内部に格納できることを意味します。これが、静的ジェネリック型を作成する理由である可能性があります。これは(かなり役に立たないが有益な)例です:

public static TypeFactory<T> where T : new()
{
    // There is one slot per T.
    private static readonly object instance = new T();

    public static object GetInstance() {
        return instance;
    }
}

string a = (string)TypeFactory<string>.GetInstance();
int b = (int)TypeFactory<int>.GetInstance();
10
Steven

私が最近学んだ静的ジェネリッククラスの1つの使用法は、型のクラスレベルで制約を定義することです。その後、制約はクラスのすべての静的メンバーに適用されます。これは静的では許可されないことも学びました。拡張メソッドを定義するジェネリッククラス。

たとえば、静的ジェネリッククラスを作成する予定で、すべてのジェネリック型がIComparableである必要があることがわかっている場合(単なる例)、次のことができます。

static class MyClass<T> where T : IComparable
{
  public static void DoSomething(T a, T b)
  {
  }

  public static void DoSomethingElse(T a, T b)
  {
  }
}

すべてのメンバーに制約を適用する必要はなく、クラスレベルでのみ適用する必要があることに注意してください。

5
Chris Taylor

あなたが言及した最初のブログ投稿は、(ActiveRecord実装の静的リポジトリクラスとしての)有効な使用法を示しています:

public static class Repository<T>
{
    public static T[] FindAll { }

    public static T GetById(int id){ }

    public static void Save(T item) { }
}

または、ジェネリック配列にさまざまな並べ替えメソッドを実装する場合:

public static class ArraySort<T>
{
    public static T[] BubbleSort(T[] source) { }

    public static T[] QuickSort(T[] source) { }
}
3
Justin Niessner

C#での静的ジェネリッククラスの主な用途は何ですか?それらはいつ使用されるべきですか?それらの使用法を最もよく表す例は何ですか?

一般に、静的クラスで型パラメーターを作成することは避けるべきだと思います。そうしないと、型推論に依存してクライアントコードをより簡潔にすることができません。

具体的な例を示すために、リストの操作を処理する静的ユーティリティクラスを作成しているとしましょう。クラスは2つの方法で書くことができます。

_// static class with generics
public static class ListOps<T>
{
    public static List<T> Filter(List<T> list, Func<T, bool> predicate) { ... }
    public static List<U> Map<U>(List<T> list, Func<T, U> convertor) { ... }
    public static U Fold<U>(List<T> list, U seed, Func<T, U> aggregator) { ... }
}

// Vanilla static class
public static class ListOps
{
    public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate) { ... }
    public static List<U> Map<T, U>(List<T> list, Func<T, U> convertor) { ... }
    public static U Fold<T, U>(List<T> list, U seed, Func<T, U> aggregator) { ... }
}
_

しかし、クラスは同等ですが、どちらが使いやすいですか?比較:

_// generic static class
List<int> numbers = Enumerable.Range(0, 100).ToList();
List<int> evens = ListOps<int>.Filter(numbers, x => x % 2 = 0);
List<string> numberString = ListOps<int>.Map(numbers, x => x.ToString());
int sumOfSquares = ListOps<int>.Fold(numbers, 0, (acc, x) => acc + x*x);

// Vanilla static class
List<int> numbers = Enumerable.Range(0, 100).ToList();
List<int> evens = ListOps.Filter(numbers, x => x % 2 = 0);
List<string> numberString = ListOps.Map(numbers, x => x.ToString());
int sumOfSquares = ListOps.Fold(numbers, 0, (acc, x) => acc + b * b);
_

私の意見では、_ListOps<someType>_はかさばって不器用です。 Vanillaクラスの定義は少し大きくなりますが、型推論のおかげでクライアントコードが読みやすくなります。

最悪の場合、C#は型を推測できないため、手動で指定する必要があります。 ListOps<A>.Map<B>(...)ListOps.Map<A, B>(...)のどちらを書きますか?私の好みは後者です。


上記の戦略は、静的クラスが可変状態なしを保持している場合、またはその可変状態がコンパイル時である場合に特にうまく機能します。

静的クラスが、コンパイル時に型が決定されない可変状態を保持している場合は、ジェネリックパラメーターを持つ静的クラスのユースケースがある可能性があります。うまくいけば、これらの機会はほとんどありませんが、それが起こったときは、C#が機能をサポートしていることに満足するでしょう。

2
Juliet

EntityFramework Asyncメソッドを使用するクラスに対してテストするときに、これらを使用してDbSetをモックします。

public static class DatabaseMockSetProvider<TEntity> where TEntity: class
{
    public static DbSet<TEntity> CreateMockedDbSet(IQueryable<TEntity> mockData)
    {
        var mockSet = Mock.Create<DbSet<TEntity>>();
        Mock.Arrange(() => ((IDbAsyncEnumerable<TEntity>)mockSet).GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<TEntity>(mockData.GetEnumerator()));
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Provider)
            .Returns(new TestDbAsyncQueryProvider<TEntity>(mockData.Provider));
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Expression).Returns(mockData.Expression);
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).ElementType).Returns(mockData.ElementType);
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).GetEnumerator()).Returns(mockData.GetEnumerator());

        return mockSet;
    }
}

そして、私の単体テストでそのように使用します-多くの時間を節約し、任意のエンティティタイプに使用できます:

var mockSet = DatabaseMockSetProvider<RecipeEntity>.CreateMockedDbSet(recipes);
        Mock.Arrange(() => context.RecipeEntities).ReturnsCollection(mockSet);
2
Lee Salter

その通りです。あまり使用されていません。おそらく、例外であるいくつかのまれなケースがあります。たとえば、Justinの例のように、クラスがタイプ固有のリポジ​​トリであるが、静的コレクションをキャッシュとして保持している場合はどうなるでしょうか。一般に、メソッドだけでなく状態が含まれている場合は、これがポイントになる可能性があります。

1
Steven Sudit

静的ジェネリッククラスは、特定の静的クラスとまったく同じように役立ちます。違いは、コピーアンドペーストを使用して、作業するタイプごとに静的クラスのバージョンを作成する必要がないことです。クラスをジェネリックにし、型パラメーターのセットごとに1つのバージョンを「生成」できます。

0
John Saunders