web-dev-qa-db-fra.com

ReSharper avertit: "Champ statique de type générique"

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

Est-ce faux? Je suppose que cela a en fait un champ static readonly Pour chacun des EnumRouteConstraint<T> Possibles que je rencontre par exemple.

243
bevacqua

C'est bien d'avoir un champ statique dans un type générique, du moment que vous savez que vous obtiendrez vraiment un champ par combinaison d'arguments de type. Je suppose que R # vous avertit au cas où vous ne le sauriez pas.

Voici un exemple de cela:

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

Comme vous pouvez le voir, Generic<string>.Foo est un champ différent de Generic<object>.Foo - ils ont des valeurs séparées.

442
Jon Skeet

Depuis le JetBrains wiki :

Dans la grande majorité des cas, le fait d'avoir un champ statique dans un type générique est le signe d'une erreur. La raison en est qu’un champ statique dans un type générique sera pas partagé entre les instances de différents types construits proches. Cela signifie que pour une classe générique C<T> qui a un champ statique X, les valeurs de C<int>.X et C<string>.X ont des valeurs complètement différentes et indépendantes.

Dans les rares cas où vous avez besoin des champs statiques "spécialisés", n'hésitez pas à supprimer l'avertissement.

Si vous avez besoin d'un champ statique partagé entre des instances avec des arguments génériques différents, définissez une classe de base non générique pour stocker vos membres statiques, puis définissez votre type générique à hériter de ce type.

140
AakashM

Ce n'est pas nécessairement une erreur - il vous avertit d'un potentielmalentend des génériques C #.

Le moyen le plus simple de se rappeler ce que font les génériques est le suivant: Les génériques sont des "modèles" pour la création de classes, tout comme les classes sont des "modèles" pour la création d'objets. (Eh bien, c’est une simplification. Vous pouvez également utiliser des génériques de méthode.)

De ce point de vue, MyClassRecipe<T> N'est pas une classe - c'est une recette, un plan détaillé, à quoi ressemblerait votre classe. Une fois que vous remplacez T par quelque chose de concret, dites int, string, etc., vous obtenez un cours. Il est parfaitement légal d'avoir un membre statique (champ, propriété, méthode) déclaré dans votre classe nouvellement créée (comme dans toute autre classe) et aucun signe d'erreur ici. À première vue, il serait quelque peu méfiant de déclarer static MyStaticProperty<T> Property { get; set; } Dans le plan de classe, mais ceci est également légal. Votre propriété serait également paramétrée, ou basée sur un modèle.

Aucune merveille dans VB les statistiques s'appellent shared. Dans ce cas, vous devez savoir que ces membres "partagés" ne sont partagés qu'entre des instances de la même classe exacte, et pas parmi les classes distinctes produites en substituant <T> avec quelque chose d'autre.

62

Il existe déjà plusieurs bonnes réponses qui expliquent l’avertissement et sa raison. Plusieurs de ces états déclarent quelque chose comme avoir un champ statique dans un type générique est généralement une erreur.

Je pensais ajouter un exemple de l’utilité de cette fonctionnalité, c’est-à-dire un cas dans lequel la suppression de la signalisation R # est logique.

Imaginez que vous souhaitiez sérialiser un ensemble de classes d'entités, dites à XML. Vous pouvez créer un sérialiseur pour cela en utilisant new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), mais vous devrez ensuite créer un sérialiseur distinct pour chaque type. En utilisant des génériques, vous pouvez remplacer cela par ce qui suit, que vous pouvez placer dans une classe générique à partir de laquelle les entités peuvent dériver:

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

Puisque vous ne voulez probablement pas générer un nouveau sérialiseur chaque fois que vous devez sérialiser une instance d'un type particulier, vous pouvez ajouter ceci:

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

Si cette classe n'était PAS générique, chaque instance de la classe utiliserait le même _typeSpecificSerializer.

Cependant, comme il est IS générique, un ensemble d’instances de même type pour T partagera une seule instance de _typeSpecificSerializer _ (qui aura été créé pour ce type spécifique), tandis que les instances avec un type différent pour T utiliseront différentes instances de _typeSpecificSerializer.

Un exemple

A condition que les deux classes qui prolongent 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; }
}

... utilisons-les:

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

Dans ce cas, sous le capot, firstInst et secondInst seront des instances de la même classe (à savoir SerializableEntity<MyFirstEntity>), et à ce titre, ils partageront une instance de _typeSpecificSerializer.

thirdInst et fourthInst sont des instances d'une classe différente (SerializableEntity<OtherEntity>), et partagera donc une instance de _typeSpecificSerializer c'est-à-dire différent des deux autres.

Cela signifie que vous obtenez différentes instances de sérialiseur pour chaque entité types, tout en les maintenant statiques dans le contexte de chaque type réel (c'est-à-dire, partagées entre des instances d'un type spécifique).

7
Kjartan