[c#] ReSharper advierte: "Campo estático en tipo genérico"



Answers

Desde la 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 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 independientes completamente diferentes.

En los casos poco frecuentes en que necesite los campos estáticos 'especializados', siéntase libre de 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.

Question
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 tiene un campo de static readonly para cada posible EnumRouteConstraint<T> que yo suceda.




Ya hay varias buenas respuestas que explican la advertencia y el motivo. Varios de estos dicen algo así como tener un campo estático en un tipo genérico generalmente 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.

Imagine que tiene un conjunto de clases de entidad que desea serializar, digamos a Xml. Puede crear un serializador para esto usando la 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))

Dado que probablemente no desee generar un nuevo serializador cada vez que necesite serializar una instancia de un tipo 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 era genérica, 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 única instancia de _typeSpecificSerializer (que se habrá creado para ese tipo específico), mientras que las instancias con un tipo diferente para T utilizarán instancias diferentes de _typeSpecificSerializer .

Un ejemplo

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

... usémoslos:

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 (a saber, SerializableEntity<MyFirstEntity> ) y, como tales, 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 entidad, al tiempo que las mantiene estáticas dentro del contexto de cada tipo real (es decir, compartidas entre instancias que son de un tipo específico).




Related