ReSharper ostrzega: „Pole statyczne w typie ogólnym”

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

Czy to źle? Zakładałbym, że to rzeczywiście ma static readonlypole dla każdej możliwej EnumRouteConstraint<T>instancji.

bevacqua
źródło
Czasami jest to cecha, a czasem irytacja. Chciałbym, żeby C # miał jakieś słowo kluczowe, aby je odróżnić
nawfal
3
Zobacz także are-static-
Members

Odpowiedzi:

468

Dobrze jest mieć pole statyczne w typie ogólnym, o ile wiesz, że naprawdę otrzymasz jedno pole na kombinację argumentów typu. Domyślam się, że R # ostrzega cię na wypadek, gdybyś nie był tego świadomy.

Oto przykład:

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

Jak widać, pole Generic<string>.Foojest inne niż Generic<object>.Foo- posiadają osobne wartości.

Jon Skeet
źródło
Jest to również prawdą, gdy klasy ogólne dziedziczą z klasy innej niż ogólna, która zawiera typy statyczne. Na przykład, jeśli utworzę class BaseFoozawierający element statyczny, to czy z niego wywnioskujesz, class Foo<T>: BaseFooczy wszystkie Foo<T>klasy mają tę samą wartość elementu statycznego?
bikeman868,
2
Odpowiadając na mój własny komentarz tutaj, ale tak, wszystkie Foo <T> będą miały tę samą wartość statyczną, jeśli są zawarte w nietypowej klasie bazowej. Zobacz dotnetfiddle.net/Wz75ya
bikeman868 18.08.17
147

Z wiki JetBrains :

W zdecydowanej większości przypadków posiadanie pola statycznego w typie ogólnym jest oznaką błędu. Powodem tego jest to, że pole statyczne w typie ogólnym nie będzie współużytkowane przez instancje różnych blisko skonstruowanych typów. Oznacza to, że dla ogólnej klasy, C<T>która ma pole statyczne X, wartości C<int>.Xi C<string>.X mają zupełnie inne, niezależne wartości.

W rzadkich przypadkach, gdy nie potrzebujemy „specjalistyczny” pól statycznych, nie krępuj się tłumić ostrzeżenie.

Jeśli potrzebujesz wspólnego pola statycznego między instancjami z różnymi ogólnymi argumentami, zdefiniuj nietypową klasę podstawową do przechowywania elementów statycznych, a następnie ustaw typ ogólny na dziedziczenie po tym typie.

AakashM
źródło
13
Stosując typ ogólny, technicznie uzyskujesz osobną i oddzielną klasę dla każdego hostowanego typu ogólnego. Deklarując dwie oddzielne, nieogólne klasy, nie spodziewałbyś się, że będziesz dzielić między nimi zmienne statyczne, więc dlaczego generyczne powinny być inne? Jedynym sposobem, który można uznać za rzadki, jest to, że większość programistów nie rozumie, co robią podczas tworzenia klas ogólnych.
Syndog,
2
@Syndog opisane zachowanie statyki w klasie ogólnej wygląda dla mnie dobrze i zrozumiałe. Myślę jednak, że powodem tych ostrzeżeń jest to, że nie każdy zespół ma tylko doświadczonych i skoncentrowanych programistów. Prawidłowy kod staje się podatny na błędy z powodu kwalifikacji programisty.
Staś Iwanow
Ale co, jeśli nie chcę tworzyć nietypowej klasy bazowej tylko do przechowywania tych pól statycznych. Czy w takim przypadku mogę po prostu ukryć ostrzeżenia?
Tom Lint
@TomLint, jeśli wiesz, co robisz, to naprawdę warto ukryć ostrzeżenia.
AakashM
65

To niekoniecznie jest błąd - ostrzega przed potencjalnym nieporozumieniem z generycznymi C #.

Najprostszym sposobem na zapamiętanie, co robią ogólne, jest: Ogólne to „plany” do tworzenia klas, podobnie jak klasy to „plany” do tworzenia obiektów. (Cóż, jest to jednak uproszczenie. Możesz także użyć ogólnych metod.)

Z tego punktu widzenia MyClassRecipe<T>nie jest to klasa - to przepis, schemat tego, jak wyglądałaby twoja klasa. Gdy podstawisz T czymś konkretnym, powiedzmy int, string itp., Otrzymasz klasę. Zgodne z prawem jest zadeklarowanie elementu statycznego (pole, właściwość, metoda) w nowo utworzonej klasie (jak w każdej innej klasie) i brak oznak błędu. Na pierwszy rzut oka byłoby to trochę podejrzane, jeśli zadeklarujesz static MyStaticProperty<T> Property { get; set; }w planie klasowym, ale jest to również legalne. Twoja właściwość również zostałaby sparametryzowana lub szablonowana.

Nic dziwnego, że w VB są nazywane shared. W takim przypadku należy jednak pamiętać, że tacy „współdzieleni” członkowie są dzieleni tylko między instancjami tej samej dokładnej klasy, a nie między odrębnymi klasami wytworzonymi przez zastąpienie <T>czymś innym.

Alexander Christov
źródło
1
Myślę, że nazwa C ++ sprawia, że ​​jest najbardziej przejrzysta ze wszystkich. W C ++ są one nazywane Szablony, czyli czym są, Szablony dla konkretnych klas.
Michael Brown
8

Jest już kilka dobrych odpowiedzi, które wyjaśniają ostrzeżenie i jego przyczynę. Kilka z nich stwierdza coś w rodzaju błędu statycznego w typie ogólnym, generalnie błąd .

Pomyślałem, że dodam przykład, w jaki sposób ta funkcja może być użyteczna, tj. Przypadek, w którym tłumienie ostrzeżenia R # ma sens.

Wyobraź sobie, że masz zestaw klas jednostek, które chcesz serializować, powiedz Xml. Możesz utworzyć do tego celu serializator new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), ale wtedy będziesz musiał utworzyć osobny serializator dla każdego typu. Używając ogólnych, możesz zamienić to na następujące, które możesz umieścić w ogólnej klasie, z której jednostki mogą pochodzić:

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

Ponieważ prawdopodobnie nie chcesz generować nowego serializatora za każdym razem, gdy musisz serializować wystąpienie określonego typu, możesz dodać to:

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

Jeśli ta klasa NIE byłaby ogólna, wówczas każda instancja klasy użyłaby tego samego _typeSpecificSerializer.

Ponieważ jest to jednak rodzaj ogólny, zestaw instancji tego samego typu dla Tbędzie współużytkować pojedyncze wystąpienie _typeSpecificSerializer(które zostanie utworzone dla tego określonego typu), podczas gdy wystąpienia o innym typie dla Tbędą używać różnych wystąpień _typeSpecificSerializer.

Przykład

Pod warunkiem, że dwie klasy, które rozszerzają 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; }
}

... użyjmy ich:

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

W tym przypadku, w obszarze okapu, firstInsti secondInstbędzie instancje tej samej klasy (tj SerializableEntity<MyFirstEntity>), i jako takie, będą dzielić wystąpienie _typeSpecificSerializer.

thirdInsti fourthInstprzypadki, w innej klasy ( SerializableEntity<OtherEntity>), a więc będą dzielić instancję _typeSpecificSerializerto różni się od pozostałych dwóch.

Oznacza to, że otrzymujesz różne instancje serializatora dla każdego z typów jednostek , jednocześnie zachowując je statycznie w kontekście każdego rzeczywistego typu (tj. Współużytkowane przez instancje określonego typu).

Kjartan
źródło
Ze względu na reguły inicjalizacji statycznej (inicjator statyczny nie jest wywoływany, dopóki klasa nie zostanie po raz pierwszy przywołana), możesz zrezygnować z kontroli w Getterze i po prostu zainicjować go w deklaracji instancji statycznej.
Michael Brown