visual studio c# asp.net-mvc-3 generics static resharper

c# - studio - ReSharper advierte: "Campo estático en tipo genérico"



resharper visual studio 2017 (4)

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

¿Esto esta mal? Supongo que esto en realidad tiene un campo de static readonly para cada una de las posibles EnumRouteConstraint<T> que suceda a la instancia.


Desde el wiki de JetBrains :

En la gran mayoría de los casos, tener un campo estático en un tipo genérico es un signo de error. La razón de esto es que un campo estático en un tipo genérico no se compartirá entre las instancias de diferentes tipos construidos cercanos. Esto significa que para una clase genérica C<T> que tiene un campo estático X , los valores de C<int>.X y C<string>.X tienen valores completamente diferentes e independientes.

En los casos excepcionales en los que necesite los campos estáticos ''especializados'', no dude en suprimir la advertencia.

Si necesita tener un campo estático compartido entre instancias con diferentes argumentos genéricos, defina una clase base no genérica para almacenar sus miembros estáticos, luego configure su tipo genérico para heredar de este tipo.


Está bien tener un campo estático en un tipo genérico, siempre y cuando sepa que realmente obtendrá un campo por combinación de argumentos de tipo. Supongo que R # solo te advierte en caso de que no lo hayas notado.

Aquí hay un ejemplo de eso:

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

Como puede ver, Generic<string>.Foo es un campo diferente de Generic<object>.Foo : contienen valores separados.


Esto no es necesariamente un error: le advierte sobre un posible malentendido de los genéricos de C #.

La forma más fácil de recordar lo que hacen los genéricos es la siguiente: los genéricos son "planos" para crear clases, al igual que las clases son "planos" para crear objetos. (Bueno, aunque esto es una simplificación. También puede usar métodos genéricos de métodos).

Desde este punto de vista, MyClassRecipe<T> no es una clase, es una receta, un plano, de cómo se vería tu clase. Una vez que sustituyes T con algo concreto, di int, string, etc., obtendrás una clase. Es perfectamente legal tener un miembro estático (campo, propiedad, método) declarado en su clase recién creada (como en cualquier otra clase) y no hay indicios de ningún error aquí. Sería algo sospechoso, a primera vista, si declara static MyStaticProperty<T> Property { get; set; } static MyStaticProperty<T> Property { get; set; } static MyStaticProperty<T> Property { get; set; } dentro de su plan de clase, pero esto también es legal. Su propiedad también estaría parametrizada o programada.

No es de extrañar que las estadísticas de VB se llamen shared . Sin embargo, en este caso, debe tener en cuenta que dichos miembros "compartidos" solo se comparten entre instancias de la misma clase exacta y no entre las distintas clases producidas al sustituir <T> con otra cosa.


Ya hay varias respuestas buenas aquí, que explican la advertencia y la razón de ello. Varios de estos estados dicen que tener un campo estático en un tipo genérico generalmente es un error .

Pensé que agregaría un ejemplo de cómo esta característica puede ser útil, es decir, un caso en el que suprimir la advertencia de R # tiene sentido.

Imagina que tienes un conjunto de clases de entidad que quieres serializar, por ejemplo a Xml. Puede crear un serializador para esto utilizando el new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)) , pero luego tendrá que crear un serializador separado para cada tipo. Usando genéricos, puede reemplazar eso con lo siguiente, que puede colocar en una clase genérica que las entidades pueden derivar de:

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

Como probablemente no desee generar un nuevo serializador cada vez que necesite serializar una instancia de un tipo en particular, puede agregar esto:

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

Si esta clase NO fuera genérica, entonces cada instancia de la clase usaría el mismo _typeSpecificSerializer .

Sin embargo, dado que es genérico, un conjunto de instancias con el mismo tipo para T compartirá una sola instancia de _typeSpecificSerializer (que se habrá creado para ese tipo específico), mientras que las instancias con un tipo diferente para T usarán diferentes instancias de _typeSpecificSerializer .

Un ejemplo

Proporcionó las dos clases que extienden 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; } }

... vamos a usarlos:

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

En este caso, bajo el capó, firstInst y firstInst serán instancias de la misma clase (es decir, SerializableEntity<MyFirstEntity> ), y como tal, compartirán una instancia de _typeSpecificSerializer .

thirdInst y fourthInst son instancias de una clase diferente ( SerializableEntity<OtherEntity> ), por lo que compartirán una instancia de _typeSpecificSerializer que es diferente de las otras dos.

Esto significa que obtiene diferentes instancias de serializador para cada uno de sus tipos de entidades, mientras que las mantiene estáticas dentro del contexto de cada tipo real (es decir, compartido entre instancias que son de un tipo específico).