ReSharper prévient: «Champ statique de type générique»

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

Est-ce mal? Je suppose que cela a en fait un static readonlychamp pour chacun des possibles EnumRouteConstraint<T>que j'arrive à l'instance.

bevacqua
la source
Parfois, c'est une caractéristique, parfois une gêne. Je souhaitais que C # ait un mot-clé pour les distinguer
nawfal

Réponses:

468

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

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>.Fooest un champ différent de Generic<object>.Foo- ils contiennent des valeurs distinctes.

Jon Skeet
la source
Est-ce également vrai lorsque les classes génériques héritent d'une classe non générique qui contient des types statiques. Par exemple, si je crée class BaseFoocontenant un membre statique, puis en dériverai- class Foo<T>: BaseFooje toutes les Foo<T>classes partageront-elles la même valeur de membre statique?
bikeman868
2
Répondant à mon propre commentaire ici, mais oui, tous les Foo <T> auront la même valeur statique s'ils sont contenus dans une classe de base non générique. Voir dotnetfiddle.net/Wz75ya
bikeman868
147

Du wiki JetBrains :

Dans la grande majorité des cas, avoir un champ statique dans un type générique est un signe d'erreur. La raison en est qu'un champ statique dans un type générique ne 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>.Xet C<string>.X ont des valeurs indépendantes complètement différentes.

Dans les rares cas où vous ne devez les champs statiques « spécialisés », vous pouvez supprimer l'avertissement.

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

AakashM
la source
13
Lorsque vous utilisez un type générique, vous vous retrouvez techniquement avec une classe distincte et distincte pour chaque type générique que vous hébergez. Lorsque vous déclarez deux classes distinctes non génériques, vous ne vous attendez pas à partager des variables statiques entre elles, alors pourquoi les génériques devraient-ils être différents? La seule façon de considérer cela comme rare est que la majorité des développeurs ne comprennent pas ce qu'ils font lors de la création de classes génériques.
Syndog
2
@Syndog le comportement décrit de la statique au sein d'une classe générique me semble bien et compréhensible. Mais je suppose que la raison derrière ces avertissements est que toutes les équipes n'ont que des développeurs expérimentés et concentrés. Le code correct devient sujet aux erreurs en raison de la qualification du développeur.
Stas Ivanov
Mais que faire si je ne veux pas créer une classe de base non générique juste pour contenir ces champs statiques. Puis-je simplement supprimer les avertissements, dans ce cas?
Tom Lint
@TomLint si vous savez ce que vous faites, la suppression des avertissements est en effet la chose à faire.
AakashM
65

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

La façon la plus simple de se souvenir de ce que font les génériques est la suivante: Les génériques sont des "plans" pour créer des classes, tout comme les classes sont des "plans" pour créer des objets. (Eh bien, c'est une simplification cependant. Vous pouvez également utiliser des génériques de méthode.)

De ce point de vue, ce MyClassRecipe<T>n'est pas une classe - c'est une recette, un plan directeur, à quoi ressemblerait votre classe. Une fois que vous remplacez T par quelque chose de concret, disons int, string, etc., vous obtenez une classe. 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. Ce serait un peu suspect, à première vue, si vous déclarez static MyStaticProperty<T> Property { get; set; }dans votre plan de classe, mais c'est légal aussi. Votre propriété serait également paramétrée ou modélisée.

Pas étonnant que la statique VB soit appelée shared. Dans ce cas cependant, vous devez être conscient que ces membres "partagés" ne sont partagés qu'entre des instances de la même classe exacte, et non entre les classes distinctes produites en les remplaçant <T>par autre chose.

Alexander Christov
la source
1
Je pense que le nom C ++ le rend le plus clair de tous. En C ++, ils sont appelés Templates, ce qui est ce qu'ils sont, des Templates pour des classes concrètes.
Michael Brown
8

Il y a déjà plusieurs bonnes réponses ici, qui expliquent l'avertissement et la raison de celui-ci. Plusieurs de ceux-ci indiquent quelque chose comme avoir un champ statique dans un type générique généralement une erreur .

J'ai pensé ajouter un exemple de la façon dont cette fonctionnalité peut être utile, c'est-à-dire un cas où la suppression de l'avertissement R # est logique.

Imaginez que vous ayez un ensemble de classes d'entité que vous souhaitez sérialiser, par exemple en 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 dont les entités peuvent dériver:

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

Étant donné que vous ne souhaitez 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, alors chaque instance de la classe utiliserait la même _typeSpecificSerializer.

Puisqu'il est générique cependant, un ensemble d'instances avec le même type pour Tpartagera une seule instance de _typeSpecificSerializer(qui aura été créée pour ce type spécifique), tandis que les instances avec un type différent pour Tutiliseront différentes instances de _typeSpecificSerializer.

Un exemple

A condition que les deux classes qui s'étendent 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, firstInstet secondInstseront des instances de la même classe (à savoir SerializableEntity<MyFirstEntity>), et en tant que tels, ils partageront une instance de _typeSpecificSerializer.

thirdInstet fourthInstsont des instances d'une classe différente ( SerializableEntity<OtherEntity>), et partageront donc une instance _typeSpecificSerializerqui est différente des deux autres.

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

Kjartan
la source
En raison des règles d'initialisation statique (l'initialiseur statique n'est appelé que lorsque la classe est référencée pour la première fois), vous pouvez renoncer à la vérification dans le Getter et l'initialiser simplement dans la déclaration d'instance statique.
Michael Brown