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


261
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>あるそれぞれのフィールドを持っていると思います。


時々その機能、時には煩わしさ。私はC#にそれらを区別するためのいくつかのキーワードがあればいいのにと思っていました
nawfal

回答:


468

型引数の組み合わせごとに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>.Fooとは別のフィールドですGeneric<object>.Foo-それらは別々の値を保持しています。


ジェネリッククラスが静的型を含む非ジェネリッククラスから継承する場合も同様です。たとえばclass BaseFoo、静的メンバーを含むものを作成した場合、そこから派生するclass Foo<T>: BaseFooと、すべてのFoo<T>クラスが同じ静的メンバー値を共有しますか?
bikeman868 2017

2
ここで自分のコメントに答えますが、はい、すべてのFoo <T>が非ジェネリック基本クラスに含まれている場合、すべての静的値は同じ静的値になります。dotnetfiddle.net/Wz75yaを
bikeman868

147

JetBrains wikiから:

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

「特殊な」静的フィールド必要なまれなケースでは、警告を抑制してもかまいません

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


13
ジェネリック型を採用する場合、技術的には、ホストしているジェネリック型ごとに個別のクラスが作成されます。2つの個別の非ジェネリッククラスを宣言する場合、それらの間で静的変数を共有するとは思わないので、ジェネリックが異なる必要があるのはなぜですか?これがまれであると考えられる唯一の方法は、大多数の開発者がジェネリッククラスを作成するときに何をしているのか理解していない場合です。
Syndog 2014

2
@Syndog汎用クラス内の静的の記述された振る舞いは、私にはうまく理解できます。しかし、これらの警告の背後にある理由は、すべてのチームが経験豊富で集中的な開発者しかいないということではないと思います。開発者の資格により、正しいコードはエラーが発生しやすくなります。
Stas Ivanov

しかし、これらの静的フィールドを保持するためだけに非ジェネリック基本クラスを作成したくない場合はどうでしょうか。この場合、警告を抑制できますか?
トム・リント

@TomLint何をしているのかわかっている場合、警告を抑制することは実際に行うことです。
AakashM

65

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

ジェネリックスが何をするかを覚える最も簡単な方法は次のとおりです。ジェネリックスは、クラスがオブジェクトを作成するための「ブループリント」と同様に、クラスを作成するための「ブループリント」です。(しかし、これは単純化です。メソッドのジェネリックを使用することもできます。)

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

VB staticが呼び出されるのも不思議ではありませんshared。ただし、この場合、そのような「共有」メンバーは、まったく同じクラスのインスタンス間でのみ共有され<T>、他のもので置換することによって生成される個別のクラス間では共有されないことに注意する必要があります。


1
私はC ++の名前がす​​べてを最も明確にしていると思います。C ++では、これらはテンプレートと呼ばれ、具体的なクラスのテンプレートと呼ばれます。
マイケルブラウン

8

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

この機能がどのように役立つか、つまり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ます。

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

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

// 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();

この場合、ボンネットの下、firstInst及びsecondInst(つまり同じクラスのインスタンスとなりSerializableEntity<MyFirstEntity>)、そのようなものとして、それらはのインスタンスを共有します_typeSpecificSerializer

thirdInstおよびfourthInstは異なるクラス(SerializableEntity<OtherEntity>)のインスタンスなので、他の2つ_typeSpecificSerializerとは異なるインスタンスを共有します。

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


静的初期化の規則のため(クラスが最初に参照されるまで静的初期化子は呼び出されません)、ゲッターでのチェックを省略して、静的インスタンス宣言でそれを初期化するだけで済みます。
マイケルブラウン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.