ReSharper warnt: "Statisches Feld im generischen Typ"

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

Ist das falsch? Ich würde annehmen, dass dies tatsächlich ein static readonlyFeld für jede der möglichen EnumRouteConstraint<T>Instanzen hat, die ich gerade habe.

bevacqua
quelle
Manchmal ist es ein Merkmal, manchmal ein Ärger. Ich wünschte, C # hätte ein Schlüsselwort, um sie zu unterscheiden
nawfal

Antworten:

468

Es ist in Ordnung, ein statisches Feld in einem generischen Typ zu haben, solange Sie wissen, dass Sie wirklich ein Feld pro Kombination von Typargumenten erhalten. Ich vermute, dass R # Sie nur warnt, falls Sie sich dessen nicht bewusst waren.

Hier ist ein Beispiel dafü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
    }
}

Wie Sie sehen können, Generic<string>.Fooist ein anderes Feld als Generic<object>.Foo- sie enthalten separate Werte.

Jon Skeet
quelle
Gilt dies auch, wenn generische Klassen von einer nicht generischen Klasse erben, die statische Typen enthält? Wenn ich beispielsweise class BaseFooein statisches Element mit einem statischen Element erstelle , class Foo<T>: BaseFoohaben alle Foo<T>Klassen denselben statischen Elementwert?
Bikeman868
2
Wenn ich hier meinen eigenen Kommentar beantworte, aber ja, haben alle Foo <T> den gleichen statischen Wert, wenn er in einer nicht generischen Basisklasse enthalten ist. Siehe dotnetfiddle.net/Wz75ya
bikeman868
147

Aus dem JetBrains-Wiki :

In den allermeisten Fällen ist ein statisches Feld in einem generischen Typ ein Zeichen für einen Fehler. Der Grund dafür ist, dass ein statisches Feld in einem generischen Typ nicht von Instanzen verschiedener eng konstruierter Typen gemeinsam genutzt wird. Dies bedeutet, dass für eine generische Klasse C<T>mit einem statischen Feld Xdie Werte von C<int>.XundC<string>.X völlig unterschiedliche, unabhängige Werte haben.

In den seltenen Fällen, wenn Sie dies tun die ‚spezialisiert‘ statische Felder benötigen, fühlen sich frei , um die Warnung zu unterdrücken.

Wenn Sie ein statisches Feld benötigen, das von Instanzen mit verschiedenen generischen Argumenten gemeinsam genutzt wird, definieren Sie eine nicht generische Basisklasse zum Speichern Ihrer statischen Mitglieder und legen Sie dann Ihren generischen Typ so fest, dass er von diesem Typ erbt.

AakashM
quelle
13
Wenn Sie einen generischen Typ verwenden, erhalten Sie technisch gesehen für jeden generischen Typ, den Sie hosten, eine eigene und separate Klasse. Wenn Sie zwei separate, nicht generische Klassen deklarieren, würden Sie nicht erwarten, statische Variablen zwischen ihnen zu teilen. Warum sollten Generika also anders sein? Dies kann nur dann als selten angesehen werden, wenn die Mehrheit der Entwickler nicht versteht, was sie beim Erstellen generischer Klassen tun.
Syndog
2
@Syndog Das beschriebene Verhalten der Statik innerhalb einer generischen Klasse sieht für mich gut und verständlich aus. Aber ich denke, der Grund für diese Warnungen ist, dass nicht jedes Team nur erfahrene und fokussierte Entwickler hat. Der richtige Code wird aufgrund der Entwicklerqualifikation fehleranfällig.
Stas Ivanov
Aber was ist, wenn ich keine nicht generische Basisklasse erstellen möchte, nur um diese statischen Felder zu speichern? Kann ich in diesem Fall nur die Warnungen unterdrücken?
Tom Lint
@ TomLint Wenn Sie wissen, was Sie tun, ist es in der Tat das Richtige, die Warnungen zu unterdrücken.
AakashM
65

Dies ist nicht unbedingt ein Fehler - es warnt Sie vor einem möglichen Missverständnis von C # -Generika.

Der einfachste Weg, sich daran zu erinnern, was Generika tun, ist der folgende: Generika sind "Blaupausen" zum Erstellen von Klassen, ähnlich wie Klassen "Blaupausen" zum Erstellen von Objekten sind. (Nun, dies ist jedoch eine Vereinfachung. Sie können auch Methodengenerika verwenden.)

Aus dieser Sicht MyClassRecipe<T>ist es keine Klasse - es ist ein Rezept, eine Blaupause dafür, wie Ihre Klasse aussehen würde. Sobald Sie T durch etwas Konkretes ersetzen, z. B. int, string usw., erhalten Sie eine Klasse. Es ist völlig legal, ein statisches Element (Feld, Eigenschaft, Methode) in Ihrer neu erstellten Klasse (wie in jeder anderen Klasse) deklariert zu haben, und hier gibt es keine Anzeichen für einen Fehler. Es wäre auf den ersten Blick etwas verdächtig, wenn Sie erklärenstatic MyStaticProperty<T> Property { get; set; } innerhalb Ihrer Klassenplanung , aber dies ist auch legal. Ihre Immobilie wird ebenfalls parametrisiert oder als Vorlage verwendet.

Kein Wunder, dass in der VB-Statik aufgerufen wird shared. In diesem Fall sollten Sie sich jedoch bewusst sein, dass solche "gemeinsam genutzten" Mitglieder nur von Instanzen derselben exakten Klasse gemeinsam genutzt werden und nicht von den verschiedenen Klassen, die durch Ersetzen <T>durch etwas anderes erzeugt werden.

Alexander Christov
quelle
1
Ich denke, der C ++ - Name macht es am deutlichsten. In C ++ heißen sie Vorlagen, was sie sind, Vorlagen für konkrete Klassen.
Michael Brown
8

Hier gibt es bereits einige gute Antworten, die die Warnung und den Grund dafür erklären. Einige von diesen geben an, dass ein statisches Feld in einem generischen Typ im Allgemeinen ein Fehler ist .

Ich dachte, ich würde ein Beispiel hinzufügen, wie diese Funktion nützlich sein kann, dh ein Fall, in dem das Unterdrücken der R # -Warnung sinnvoll ist.

Stellen Sie sich vor, Sie haben eine Reihe von Entitätsklassen, die Sie serialisieren möchten, beispielsweise Xml. Sie können hierfür einen Serializer erstellen new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), müssen dann jedoch für jeden Typ einen eigenen Serializer erstellen. Mithilfe von Generika können Sie dies durch Folgendes ersetzen, das Sie in eine generische Klasse einfügen können, von der Entitäten abgeleitet werden können:

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

Da Sie wahrscheinlich nicht jedes Mal einen neuen Serializer generieren möchten, wenn Sie eine Instanz eines bestimmten Typs serialisieren müssen, können Sie Folgendes hinzufügen:

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

Wenn diese Klasse NICHT generisch wäre, würde jede Instanz der Klasse dieselbe verwenden _typeSpecificSerializer.

Da es jedoch generisch ist, wird eine Reihe von Instanzen mit demselben Typ für Teine einzelne Instanz von _typeSpecificSerializer(die für diesen bestimmten Typ erstellt wurde) gemeinsam nutzen, während Instanzen mit einem anderen Typ für Tunterschiedliche Instanzen von verwenden_typeSpecificSerializer .

Ein Beispiel

Vorausgesetzt, die zwei Klassen, die sich erweitern 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; }
}

... lass sie uns benutzen:

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

In diesem Fall wird unter der Haube, firstInstund secondInstwerden Instanzen derselben Klasse (nämlich sein SerializableEntity<MyFirstEntity>), und als solche werden sie eine Instanz teilen _typeSpecificSerializer.

thirdInstund fourthInstsind Instanzen einer anderen Klasse ( SerializableEntity<OtherEntity>), und so wird eine Instanz teilen von _typeSpecificSerializerdas ist verschieden von den anderen beiden.

Das heißt , Sie verschiedene Serializer-Instanzen für jede Ihrer Einheit erhalten Typen , während sie noch statisch im Rahmen des jeweiligen Ist - Typs (dh gemeinsam von den Instanzen , die von einem bestimmten Typ sind) zu halten.

Kjartan
quelle
Aufgrund der Regeln für die statische Initialisierung (der statische Initialisierer wird erst aufgerufen, wenn die Klasse zum ersten Mal referenziert wird) können Sie auf die Prüfung im Getter verzichten und sie einfach in der statischen Instanzdeklaration initialisieren.
Michael Brown