ReSharper предупреждает: «Статическое поле в универсальном типе» - PullRequest
241 голосов
/ 10 марта 2012
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>, с которыми я случайно столкнулся.

Ответы [ 4 ]

437 голосов
/ 10 марта 2012

Хорошо иметь статическое поле в универсальном типе, если вы знаете, что вы действительно получите одно поле на комбинацию аргументов типа.Я предполагаю, что 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 - они содержат отдельные значения.

140 голосов
/ 12 марта 2012

Из JetBrains wiki :

В подавляющем большинстве случаев наличие статического поля в универсальном типе является признаком ошибки.Причина этого заключается в том, что статическое поле в универсальном типе будет , а не совместно использоваться экземплярами различных закрытых типов.Это означает, что для универсального класса C<T>, который имеет статическое поле X, значения C<int>.X и C<string>.X имеют совершенно разные, независимые значения.

В редких случаях, когда вы do нужны «специализированные» статические поля, не стесняйтесь подавлять предупреждение.

Если вам нужно, чтобы статическое поле было общим для экземпляров с разными универсальными аргументами, определите неуниверсальный базовый класс для хранения ваших статических членов, а затем установите универсальный тип для наследования от этого типа.

61 голосов
/ 07 июня 2013

Это не обязательно ошибка - она ​​предупреждает вас о потенциальном недопонимании обобщений C #.

Самый простой способ запомнить, что делают дженерики, это: Обобщения являются «чертежами» для создания классов, так же, как классы являются «чертежами» для создания объектов. (Что ж, это упрощение. Вы также можете использовать обобщенные методы.)

С этой точки зрения MyClassRecipe<T> - это не класс, это рецепт, план того, как будет выглядеть ваш класс. Как только вы замените T чем-то конкретным, скажем, int, string и т. Д., Вы получите класс. Вполне допустимо, чтобы статический член (поле, свойство, метод) был объявлен во вновь созданном классе (как и в любом другом классе), и здесь не было никаких признаков какой-либо ошибки. На первый взгляд, было бы несколько подозрительно, если бы вы объявили static MyStaticProperty<T> Property { get; set; } в своем проекте класса, но это тоже законно. Ваша собственность будет также параметризована или шаблонизирована.

Не зря в VB статики называются shared. В этом случае, однако, вы должны знать, что такие «общие» члены разделяются только между экземплярами одного и того же класса, а не между различными классами, полученными путем замены <T> чем-то другим.

7 голосов
/ 14 июля 2016

Здесь уже есть несколько хороших ответов, которые объясняют предупреждение и причину его. Некоторые из этих состояний что-то вроде , имеющие статическое поле в универсальном типе, обычно являются ошибкой .

Я подумал, что добавлю пример того, как эта функция может быть полезна, то есть случай, когда подавление предупреждения 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, который отличается от двух других.

Это означает, что вы получаете разные экземпляры сериализатора для каждого из ваших объектов типов , сохраняя при этом их статичность в контексте каждого фактического типа (т. Е. Разделяемые между экземплярами определенного типа).

...