ReSharper警告:“泛型中的静态字段”

本文关键字:静态 字段 泛型 警告 ReSharper | 更新日期: 2023-09-27 18:27:16

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

这有错吗?我假设这实际上对我碰巧实例的每个可能EnumRouteConstraint<T>都有一个static readonly字段。

ReSharper警告:“泛型中的静态字段”

在泛型类型中有一个静态字段是可以的,只要你知道每个类型参数组合都会得到一个字段。我的猜测是,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字段不同 - 它们包含单独的值。

来自 JetBrains wiki:

在绝大多数情况下,在泛型类型中具有静态字段 是错误的标志。其原因是静态字段在 泛型类型不会在不同关闭的实例之间共享 构造类型。这意味着对于泛型类C<T> 具有静态字段XC<int>.XC<string>.X的值 具有完全不同的独立价值观。

在极少数情况下,当您确实需要"专用"静态字段时, 随意禁止显示警告。

如果您需要在实例之间共享静态字段 不同的泛型参数,定义一个非泛型基类 存储静态成员,然后将泛型类型设置为继承自 这种类型。

这不一定是错误 - 它警告您存在 C# 泛型的潜在误解

记住泛型功能的最简单方法如下:泛型是创建类的"蓝图",就像类是创建对象的"蓝图"一样。(嗯,这是一个简化。您也可以使用方法泛型。

从这个角度来看,MyClassRecipe<T>不是一个类——它是一个食谱,一个蓝图,你的类会是什么样子。一旦你用具体的东西替换T,比如int,string等,你就会得到一个类。在新创建的类(与任何其他类一样(中声明静态成员(字段、属性、方法是完全合法的(,并且此处没有任何错误的迹象。乍一看,如果您在类蓝图中声明static MyStaticProperty<T> Property { get; set; },这有点可疑,但这也是合法的。您的属性也会被参数化或模板化。

难怪在 VB 中静力学被称为 shared .但是,在这种情况下,您应该知道,这种"共享"成员仅在同一确切类的实例之间共享,而不是在用其他内容替换<T>生成的不同类之间共享。

这里已经有几个很好的答案,解释了警告及其原因。其中一些状态类似于在泛型类型中具有静态字段通常是一个错误

我想我会添加一个示例来说明此功能如何有用,即抑制 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();

在这种情况下,在引擎盖下,firstInstsecondInst 将是同一类的实例(即 SerializableEntity<MyFirstEntity> (,因此,它们将共享 _typeSpecificSerializer 的实例。

thirdInstfourthInst 是不同类 (SerializableEntity<OtherEntity>( 的实例,因此将共享一个与其他两个不同的_typeSpecificSerializer实例。

这意味着您可以为每个实体类型获得不同的序列化程序实例,同时在每个实际类型的上下文中仍保持它们静态(即在特定类型的实例之间共享(。