web-dev-qa-db-ja.com

ReSharperは警告する:「ジェネリック型の静的フィールド」

public class EnumRouteConstraint<T> : IRouteConstraint
    where T : struct
{
    private static readonly Lazy<HashSet<string>> _enumNames; // <--

    static EnumRouteConstraint()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(Resources.Error.EnumRouteConstraint.FormatWith(typeof(T).FullName));
        }

        string[] names = Enum.GetNames(typeof(T));
        _enumNames = new Lazy<HashSet<string>>(() => new HashSet<string>
        (
            names.Select(name => name), StringComparer.InvariantCultureIgnoreCase
        ));
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        bool match = _enumNames.Value.Contains(values[parameterName].ToString());
        return match;
    }
}

これは間違っていますか?これには、実際にインスタンスに発生する可能性のあるstatic readonlyのそれぞれに対してEnumRouteConstraint<T>フィールドがあると仮定します。

243
bevacqua

型引数の組み合わせごとに1つのフィールドを実際に取得することがわかっている限り、ジェネリック型の静的フィールドを使用しても構いません。私の推測では、R#はあなたがそれを知らなかった場合に警告しているだけです。

以下に例を示します。

using System;

public class Generic<T>
{
    // Of course we wouldn't normally have public fields, but...
    public static int Foo;
}

public class Test
{
    public static void Main()
    {
        Generic<string>.Foo = 20;
        Generic<object>.Foo = 10;
        Console.WriteLine(Generic<string>.Foo); // 20
    }
}

ご覧のとおり、Generic<string>.FooGeneric<object>.Fooとは異なるフィールドです-それらは別々の値を保持します。

442
Jon Skeet

JetBrains wiki から:

ほとんどの場合、ジェネリック型の静的フィールドを持つことはエラーの兆候です。この理由は、ジェネリック型の静的フィールドは、異なる近接構築型のインスタンス間でnot共有されるためです。これは、静的フィールドXを持つ汎用クラスC<T>の場合、C<int>.XC<string>.Xの値は完全に異なる独立した値を持つことを意味します。

doが「特別な」静的フィールドを必要とするまれなケースでは、気軽に警告を抑制してください。

異なるジェネリック引数を持つインスタンス間で静的フィールドを共有する必要がある場合は、non-generic基本クラスを定義して静的メンバーを保存し、この型から継承するジェネリック型。

140
AakashM

これは必ずしもエラーではありません。C#ジェネリックの可能性誤解について警告しています。

ジェネリックが行うことを覚える最も簡単な方法は、次のとおりです。ジェネリックは、クラスを作成するための「設計図」であり、クラスがオブジェクトを作成するための「設計図」によく似ています。 (ただし、これは単純化です。メソッドジェネリックも使用できます。)

この観点からMyClassRecipe<T>はクラスではなく、クラスがどのように見えるかを示すレシピ、設計図です。 Tを具体的なもの、たとえばint、stringなどで置き換えると、クラスが得られます。静的メンバー(フィールド、プロパティ、メソッド)を(他のクラスのように)新しく作成されたクラスで宣言し、ここでエラーの兆候を持たないことは完全に合法です。クラスブループリント内でstatic MyStaticProperty<T> Property { get; set; }を宣言すると、一見するとやや疑わしいでしょうが、これも合法です。プロパティもパラメータ化またはテンプレート化されます。

VB静的変数がsharedと呼ばれるのも当然です。ただし、この場合、このような「共有」メンバーは、まったく同じクラスのインスタンス間でのみ共有され、<T>を別のもので置き換えることによって生成される個別のクラス間では共有されないことに注意してください。

62

警告とその理由を説明するいくつかの良い答えがすでにここにあります。これらのいくつかは、一般的なタイプの静的フィールドを持つことは一般的に間違いですのようなものです。

この機能がどのように役立つか、つまりR#警告を抑制することが理にかなっている場合の例を追加すると思いました。

たとえば、Xmlにシリアル化するエンティティクラスのセットがあるとします。 new XmlSerializerFactory().CreateSerializer(typeof(SomeClass))を使用してこのためのシリアライザーを作成できますが、タイプごとに個別のシリアライザーを作成する必要があります。ジェネリックを使用して、それを次のものに置き換えることができます。これは、エンティティが派生できるジェネリッククラスに配置できます。

new XmlSerializerFactory().CreateSerializer(typeof(T))

特定の型のインスタンスをシリアル化する必要があるたびに新しいシリアライザーを生成する必要はおそらくないので、これを追加できます。

public class SerializableEntity<T>
{
    // ReSharper disable once StaticMemberInGenericType
    private static XmlSerializer _typeSpecificSerializer;

    private static XmlSerializer TypeSpecificSerializer
    {
        get
        {
            // Only create an instance the first time. In practice, 
            // that will mean once for each variation of T that is used,
            // as each will cause a new class to be created.
            if ((_typeSpecificSerializer == null))
            {
                _typeSpecificSerializer = 
                    new XmlSerializerFactory().CreateSerializer(typeof(T));
            }

            return _typeSpecificSerializer;
        }
    }

    public virtual string Serialize()
    {
        // .... prepare for serializing...

        // Access _typeSpecificSerializer via the property, 
        // and call the Serialize method, which depends on 
        // the specific type T of "this":
        TypeSpecificSerializer.Serialize(xmlWriter, this);
     }
}

このクラスがジェネリックではない場合、クラスの各インスタンスは同じ_typeSpecificSerializerを使用します。

ただし、ISジェネリックなので、Tと同じ型のインスタンスのセットは、_typeSpecificSerializer(その特定の型に対して作成された)の単一のインスタンスを共有します。 Tのタイプが異なるインスタンスは、_typeSpecificSerializerの異なるインスタンスを使用します。

SerializableEntity<T>を拡張する2つのクラスを提供しました。

// Note that T is MyFirstEntity
public class MyFirstEntity : SerializableEntity<MyFirstEntity>
{
    public string SomeValue { get; set; }
}

// Note that T is OtherEntity
public class OtherEntity : SerializableEntity<OtherEntity >
{
    public int OtherValue { get; set; }
}

...それらを使用しましょう:

var firstInst = new MyFirstEntity{ SomeValue = "Foo" };
var secondInst = new MyFirstEntity{ SomeValue = "Bar" };

var thirdInst = new OtherEntity { OtherValue = 123 };
var fourthInst = new OtherEntity { OtherValue = 456 };

var xmlData1 = firstInst.Serialize();
var xmlData2 = secondInst.Serialize();
var xmlData3 = thirdInst.Serialize();
var xmlData4 = fourthInst.Serialize();

この場合、内部では、firstInstsecondInstは同じクラスのインスタンス(つまり、SerializableEntity<MyFirstEntity>)になり、_typeSpecificSerializerのインスタンスを共有します。

thirdInstおよびfourthInstは異なるクラス(SerializableEntity<OtherEntity>)のインスタンスであるため、differentである_typeSpecificSerializerのインスタンスを共有します二。

これは、エンティティごとに異なるシリアライザーインスタンスを取得することを意味しますtypes、ただし実際の各タイプのコンテキスト内で静的なままにします(つまり、特定のタイプのインスタンス間で共有します)。

7
Kjartan