ReSharper avverte: "Campo statico di tipo generico"


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

È sbagliato? Suppongo che questo abbia effettivamente un static readonlycampo per ciascuno dei possibili casi EnumRouteConstraint<T>che mi capita di fare.


A volte è una caratteristica, a volte un fastidio. Avrei voluto che C # avesse qualche parola chiave per distinguerli
nawfal

Risposte:


468

Va bene avere un campo statico in un tipo generico, purché tu sappia che otterrai davvero un campo per combinazione di argomenti di tipo. La mia ipotesi è che R # ti stia solo avvisando nel caso in cui non ne fossi a conoscenza.

Ecco un esempio:

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

Come puoi vedere, Generic<string>.Fooè un campo diverso da Generic<object>.Foo- contengono valori separati.


È vero anche quando le classi generiche ereditano da una classe non generica che contiene tipi statici. Ad esempio, se creo class BaseFoocontenente un membro statico, quindi da esso derivano class Foo<T>: BaseFootutte le Foo<T>classi condivideranno lo stesso valore di membro statico?
bikeman868,

2
Rispondendo al mio commento qui, ma sì, tutto Foo <T> avrà lo stesso valore statico se è contenuto in una classe base non generica. Vedi dotnetfiddle.net/Wz75ya
bikeman868 il

147

Dal wiki di JetBrains :

Nella stragrande maggioranza dei casi, avere un campo statico in un tipo generico è un segno di errore. La ragione di ciò è che un campo statico in un tipo generico non verrà condiviso tra istanze di diversi tipi costruiti vicini. Ciò significa che per una classe generica C<T>che ha un campo statico X, i valori di C<int>.XeC<string>.X hanno completamente diversi e indipendenti.

Nei rari casi in cui si fa servono i campi statici 'specializzato', sentitevi liberi di eliminare l'avviso.

Se è necessario avere un campo statico condiviso tra istanze con diversi argomenti generici, definire una classe di base non generica per memorizzare i membri statici, quindi impostare il tipo generico per ereditare da questo tipo.


13
Quando impieghi un tipo generico, tecnicamente finisci con una classe distinta e separata per ogni tipo generico che stai ospitando. Quando dichiari due classi separate, non generiche, non ti aspetti di condividere variabili statiche tra loro, quindi perché i generici dovrebbero essere diversi? L'unico modo in cui questo può essere considerato raro è se la maggior parte degli sviluppatori non capisce cosa stanno facendo durante la creazione di classi generiche.
Syndog,

2
@Syndog il comportamento descritto della statica all'interno di una classe generica mi sembra perfetto e comprensibile. Ma immagino che il motivo dietro quegli avvertimenti sia che non tutti i team hanno sperimentato e focalizzato solo gli sviluppatori. Il codice corretto diventa soggetto a errori a causa della qualifica dello sviluppatore.
Stas Ivanov,

E se non volessi creare una classe base non generica solo per contenere questi campi statici. Posso semplicemente eliminare gli avvisi, in questo caso?
Tom Lint,

@TomLint se sai cosa stai facendo, sopprimere gli avvisi è davvero la cosa da fare.
AakashM,

65

Questo non è necessariamente un errore: ti avverte di un potenziale fraintendimento dei generici C #.

Il modo più semplice per ricordare cosa fanno i generici è il seguente: I generici sono "progetti" per la creazione di classi, proprio come le classi sono "progetti" per la creazione di oggetti. (Beh, questa è una semplificazione però. Puoi anche usare metodi generici.)

Da questo punto di vista MyClassRecipe<T>non è una classe - è una ricetta, un progetto, di come sarebbe la tua classe. Una volta sostituito T con qualcosa di concreto, ad esempio int, string, ecc., Ottieni una classe. È perfettamente legale avere un membro statico (campo, proprietà, metodo) dichiarato nella classe appena creata (come in qualsiasi altra classe) e nessun segno di errore qui. A prima vista sarebbe alquanto sospettostatic MyStaticProperty<T> Property { get; set; } nel tuo progetto di classe, ma anche questo è legale. La tua proprietà verrebbe parametrizzata o anche modellata.

Non c'è da stupirsi che la statica VB sia chiamata shared. In questo caso, tuttavia, è necessario essere consapevoli del fatto che tali membri "condivisi" sono condivisi solo tra istanze della stessa classe esatta e non tra le classi distinte prodotte sostituendo <T>con qualcos'altro.


1
Penso che il nome C ++ lo renda più chiaro di tutti. In C ++ sono chiamati modelli, che è quello che sono, modelli per classi concrete.
Michael Brown,

8

Ci sono già molte buone risposte che spiegano l'avvertimento e il motivo di ciò. Molti di questi affermano qualcosa come avere un campo statico in un tipo generico generalmente un errore .

Ho pensato di aggiungere un esempio di come questa funzionalità possa essere utile, ad esempio un caso in cui sopprimere la segnalazione R # ha senso.

Immagina di avere una serie di classi di entità che vuoi serializzare, diciamo a Xml. Puoi creare un serializzatore per questo usando new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), ma poi dovrai creare un serializzatore separato per ogni tipo. Usando generics, puoi sostituirlo con il seguente, che puoi inserire in una classe generica da cui le entità possono derivare:

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

Poiché probabilmente non vuoi generare un nuovo serializzatore ogni volta che devi serializzare un'istanza di un tipo particolare, potresti aggiungere questo:

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

Se questa classe NON fosse generica, ogni istanza della classe userebbe la stessa _typeSpecificSerializer.

Poiché è generico, tuttavia, una serie di istanze con lo stesso tipo per Tcondividerà una singola istanza di _typeSpecificSerializer(che sarà stata creata per quel tipo specifico), mentre le istanze con un tipo Tdiverso utilizzeranno istanze diverse di_typeSpecificSerializer .

Un esempio

Fornito le due classi che si estendono 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; }
}

... usiamoli:

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 questo caso, sotto il cofano, firstInste secondInstsaranno istanze della stessa classe (vale a dire SerializableEntity<MyFirstEntity>), e come tali, condivideranno un'istanza di _typeSpecificSerializer.

thirdInste fourthInstsono istanze di una classe diversa ( SerializableEntity<OtherEntity>), e quindi condivideranno un'istanza _typeSpecificSerializerche è diversa dalle altre due.

Ciò significa che ottieni istanze serializzatore diverse per ciascuno dei tuoi tipi di entità , pur mantenendole statiche nel contesto di ciascun tipo effettivo (ovvero, condivise tra istanze di un tipo specifico).


A causa delle regole per l'inizializzazione statica (l'inizializzatore statico non viene chiamato fino a quando non viene fatto riferimento alla classe per la prima volta) è possibile rinunciare al controllo nel Getter e inizializzarlo nella dichiarazione dell'istanza statica.
Michael Brown,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.