ReSharper попереджає: "Статичне поле в загальному типі"


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

Це неправильно? Я б припустив, що це насправді є static readonlyполе для кожного з можливих, EnumRouteConstraint<T>що мені трапляються, наприклад,.


Іноді його особливість, іноді роздратування. Я хотів, щоб у C # було якесь ключове слово, щоб їх розрізнити
nawfal

Відповіді:


468

Добре мати статичне поле в загальному типі, доки ви знаєте, що ви дійсно отримаєте одне поле за комбінацією аргументів типу. Я здогадуюсь, що 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
    }
}

Як бачите, Generic<string>.Fooце поле, яке відрізняється від Generic<object>.Foo- вони містять окремі значення.


Чи справедливо це також, коли родові класи успадковують від негенеричного класу, який містить статичні типи. Наприклад, якщо я створюю, class BaseFooщо містить статичний член, то звідси виходять з нього, class Foo<T>: BaseFooчи всі Foo<T>класи матимуть однакове значення статичного члена?
bikeman868

2
Відповідаючи на мій власний коментар тут, але так, всі Foo <T> матимуть однакове статичне значення, якщо воно буде міститися в негенеричному базовому класі. Дивіться dotnetfiddle.net/Wz75ya
bikeman868

147

З вікі JetBrains :

У переважній більшості випадків наявність статичного поля в загальному типі є ознакою помилки. Причиною цього є те, що статичне поле в загальному типі не буде спільним серед примірників різних близьких побудованих типів. Це означає, що для загального класу, C<T>який має статичне поле X, значення C<int>.Xта C<string>.X мають абсолютно різні, незалежні значення.

У тих рідкісних випадках , коли ви дійсно потребуєте «спеціалізовані» статичних полях, НЕ соромтеся , щоб придушити попередження.

Якщо вам потрібно мати статичне поле, поділене між екземплярами з різними загальними аргументами, визначте негенеричний базовий клас для зберігання статичних членів, а потім встановіть свій загальний тип для спадкування від цього типу.


13
Використовуючи загальний тип, технічно ви отримуєте окремий і окремий клас для кожного загального типу, який ви хостите. Оголошуючи два окремі негенеричні класи, ви не сподіваєтесь ділитися статичними змінними між ними, тож чому дженерики повинні бути різними? Єдиний спосіб це можна вважати рідкісним, якщо більшість розробників не розуміють, що вони роблять, створюючи загальні класи.
Syndog

2
@Syndog описана поведінка статики в загальному класі для мене виглядає чудово і зрозуміло. Але я здогадуюсь, що причина цих попереджень полягає в тому, що не кожна команда має лише досвідчених та зосереджених розробників. Правильний код стає схильним до помилок через кваліфікацію розробника.
Стас Іванов

Але що робити, якщо я не хочу робити негенеричний базовий клас просто для утримання цих статичних полів. Чи можу я в такому випадку просто придушити попередження?
Том Лінт

@TomLint, якщо ви знаєте, що ви робите, то придушення попереджень дійсно річ.
AakashM

65

Це не обов'язково помилка - це попереджає вас про можливе непорозуміння C # generics.

Найпростіший спосіб запам’ятати те, що роблять генерики, це наступне: Генерики - це «креслення» для створення класів, так само, як класи - «креслення» для створення об’єктів. (Що ж, це спрощення, хоча ви також можете використовувати загальну методику.)

З цієї точки зору MyClassRecipe<T>це не клас - це рецепт, креслення, як виглядатиме ваш клас. Після того, як ви заміните T чимось конкретним, скажімо, int, string та ін., Ви отримаєте клас. Цілком законно, щоб статичний член (поле, властивість, метод) був оголошений у новоствореному класі (як і в будь-якому іншому класі) і тут не було жодних ознак помилок. На перший погляд, було б дещо підозрілим, якщо ви заявляєте static MyStaticProperty<T> Property { get; set; }в рамках свого класу план, але це теж законно. Ваша власність також буде параметризована або шаблонована.

Недарма в статиці VB називають shared. Однак у цьому випадку слід пам’ятати, що такі «спільні» члени поділяються лише між екземплярами одного і того ж точного класу, а не між окремими класами, що утворюються заміною <T>чимось іншим.


1
Я думаю, що ім'я C ++ робить його найяснішим з усіх. У C ++ вони називаються Шаблони, тобто такі, якими вони є, Шаблони для конкретних класів.
Майкл Браун

8

Тут уже є кілька хороших відповідей, які пояснюють попередження та причину цього. Кілька з них констатують щось на зразок наявності статичного поля в загальному типі, як правило, помилка .

Я думав, що додам приклад того, як ця функція може бути корисною, тобто випадок, коли придушення попередження R # має сенс.

Уявіть, у вас є набір класів сутностей, які ви хочете серіалізувати, скажімо, у Xml. Ви можете створити серіалізатор для цього, використовуючи new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), але тоді вам доведеться створити окремий серіалізатор для кожного типу. Використовуючи дженерики, ви можете замінити це на наступне, яке ви можете розмістити в загальному класі, з якого можуть бути отримані сутності:

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

Оскільки ви, ймовірно, не бажаєте створювати новий серіалізатор щоразу, коли вам потрібно серіалізувати екземпляр певного типу, ви можете додати це:

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

Якби цей клас НЕ був загальним, то кожен екземпляр класу використовував би те саме _typeSpecificSerializer .

Однак, оскільки це загальне значення, набір екземплярів одного типу для Tбуде мати спільний доступ до одного екземпляра _typeSpecificSerializer(який буде створено для цього конкретного типу), тоді як екземпляри з іншим типом Tбудуть використовувати різні екземпляри_typeSpecificSerializer .

Приклад

Надано два класи, які розширюються 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; }
}

... давайте використовувати їх:

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

У цьому випадку під капотом firstInstі secondInstбудуть екземпляри одного класу (а саме SerializableEntity<MyFirstEntity>), і як такі вони поділять екземпляр _typeSpecificSerializer.

thirdInstі fourthInstє екземплярами іншого класу ( SerializableEntity<OtherEntity>), і таким чином спільний екземпляр _typeSpecificSerializerбуде іншим від двох інших.

Це означає, що ви отримуєте різні екземпляри серіалізатора для кожного з типів вашої сутності , зберігаючи їх статичними в контексті кожного фактичного типу (тобто поділяються між екземплярами певного типу).


Через правила статичної ініціалізації (статичний ініціалізатор не викликається до першого посилання на клас), ви можете відмовитися від перевірки в Getter і просто ініціалізувати його в декларації статичного екземпляра.
Майкл Браун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.