ReSharper предупреждает: "Статическое поле в родовом типе"

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>, с которыми я случайно столкнулся.

Ответ 1

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

Ответ 2

Из JetBrains wiki:

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Я думал, что добавлю пример того, как эта функция может быть полезна, т.е. случай, когда подавление R # -warning имеет смысл.

Представьте, что у вас есть набор сущ. классов, которые вы хотите сериализовать, скажем, в Xml. Вы можете создать сериализатор для этого с помощью new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), но тогда вам нужно будет создать отдельный сериализатор для каждого типа. Используя generics, вы можете заменить его следующим: вы можете поместить в общий класс, из которого могут образовываться объекты:

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, который отличается от двух других.

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