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 для каждого из ваших типов сущностей, сохраняя при этом их статичность в контексте каждого фактического типа (то есть, совместно используемого экземплярами определенного типа).