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

Posted

技术标签:

【中文标题】ReSharper 警告:“泛型类型的静态字段”【英文标题】:ReSharper warns: "Static field in generic type" 【发布时间】:2012-03-27 16:56:17 【问题描述】:
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&lt;T&gt;

【问题讨论】:

有时它是一个功能,有时是一个烦恼。我希望 C# 有一些关键字来区分它们 另见are-static-members-of-a-generic-class-tied-to-the-specific-instance 【参考方案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&lt;string&gt;.FooGeneric&lt;object&gt;.Foo 是不同的字段 - 它们拥有不同的值。

【讨论】:

泛型类继承自包含静态类型的非泛型类时是否也是如此。例如,如果我创建包含一个静态成员的class BaseFoo,然后从它派生class Foo&lt;T&gt;: BaseFoo,所有Foo&lt;T&gt; 类会共享相同的静态成员值吗? 在这里回答我自己的评论,但是是的,如果所有 Foo 包含在非泛型基类中,它将具有相同的静态值。见dotnetfiddle.net/Wz75ya【参考方案2】:

来自JetBrains wiki:

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

在极少数情况下,当您确实需要“专门”静态字段时, 随意压制警告。

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

【讨论】:

在使用泛型类型时,从技术上讲,您最终会为您托管的每个泛型类型创建一个独特且单独的类。当声明两个独立的非泛型类时,您不会期望在它们之间共享静态变量,那么为什么泛型应该有所不同呢?如果大多数开发人员在创建泛型类时不了解他们在做什么,那么这种情况很少见。 @Syndog 所描述的泛型类中的静态行为对我来说看起来很好并且可以理解。但我想这些警告背后的原因是,并非每个团队都只有经验丰富且专注的开发人员。由于开发者的资质,正确的代码变得容易出错。【参考方案3】:

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

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

从这个角度来看,MyClassRecipe&lt;T&gt; 不是一个类——它是你的类应该是什么样子的配方、蓝图。一旦你用具体的东西代替 T,比如 int、string 等,你就会得到一个类。在新创建的类中声明一个静态成员(字段、属性、方法)是完全合法的(就像在任何其他类中一样),并且这里没有任何错误的迹象。 乍一看,如果您在班级蓝图中声明static MyStaticProperty&lt;T&gt; Property get; set; ,这会有些可疑,但这也是合法的。您的属性也将被参数化或模板化。

难怪在 VB 中静态被称为shared。但是,在这种情况下,您应该注意,此类“共享”成员仅在同一个确切类的实例之间共享,而不在通过用其他东西替换 &lt;T&gt; 产生的不同类之间共享。

【讨论】:

我认为 C++ 名称最清楚。在 C++ 中,它们被称为模板,它们就是这样,具体类的模板。【参考方案4】:

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

我想我会添加一个示例来说明此功能如何有用,即抑制 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 的单个实例(将为该特定类型创建),而对于 @ 具有不同类型的实例987654327@ 将使用_typeSpecificSerializer 的不同实例。

一个例子

提供了扩展SerializableEntity&lt;T&gt;的两个类:

// 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&lt;MyFirstEntity&gt;),因此,它们将共享_typeSpecificSerializer 的实例。

thirdInstfourthInst 是不同类 (SerializableEntity&lt;OtherEntity&gt;) 的实例,因此将共享一个与其他两个不同_typeSpecificSerializer 实例。

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

【讨论】:

由于静态初始化的规则(静态初始化器在类第一次被引用之前不会被调用),你可以放弃在 Getter 中的检查,只在静态实例声明中初始化它。跨度>

以上是关于ReSharper 警告:“泛型类型的静态字段”的主要内容,如果未能解决你的问题,请参考以下文章

创建一个泛型类型以在 Resharper 插件中查找实现

来自 ReSharper 的“从不使用自动属性访问器”警告

修改 ReSharper 中的关闭警告

Resharper 中的警告“未使用纯方法的返回值”

如何修复:在闭包 resharper 警告中访问 foreach 变量?

Resharper 警告空字符串 (System.NullReferenceException)