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


261
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>实例都有一个字段。


有时是功能,有时是烦人。我希望C#可以使用一些关键字来区分它们
nawfal 2013年

Answers:


468

泛型类型中有一个静态字段就可以了,只要您知道每种类型参数的组合实际上都会得到一个字段。我的猜测是,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-它们具有不同的值。


当泛型类从包含静态类型的非泛型类继承时,也是如此。例如,如果我创建class BaseFoo一个包含静态成员的对象,那么从中派生class Foo<T>: BaseFoo所有Foo<T>类将共享相同的静态成员值吗?
bikeman868

2
在这里回答我自己的评论,但是是的,如果所有Foo <T>包含在非通用基类中,则将具有相同的静态值。参见dotnetfiddle.net/Wz75ya
bikeman868 '17

147

JetBrains Wiki

在大多数情况下,具有泛型类型的静态字段是错误的征兆。这样做的原因是,通用类型的静态字段不会在不同的紧密构造类型的实例之间共享。这意味着,对于一个泛型类C<T>是具有静态字段X的值C<int>.X,并C<string>.X 有完全不同的,独立的价值观。

在极少数情况下,当您确实需要“特殊”静态字段时,请随时屏蔽警告。

如果需要在具有不同通用参数的实例之间共享静态字段,请定义一个非通用基类来存储您的静态成员,然后将通用类型设置为从该类型继承。


13
在采用泛型类型时,从技术上讲,您为要托管的每种泛型类型最终得到一个单独的单独类。当声明两个单独的非泛型类时,您不会期望在它们之间共享静态变量,那么泛型为什么要有所不同?唯一的这种罕见的方法是,如果大多数开发人员在创建泛型类时都不了解他们在做什么。
Syndog 2014年

2
@Syndog对泛型类中的静态行为的描述对我来说很好并且可以理解。但是我想这些警告背后的原因是,并非每个团队都只有经验丰富且专注的开发人员。由于开发人员的资格,正确的代码容易出错。
Stas Ivanov

但是,如果我不想为了容纳这些静态字段而创建非泛型基类,该怎么办?在这种情况下,我可以禁止显示警告吗?
汤姆·林特

@TomLint如果您知道自己在做什么,那么抑制警告确实是要做的事情。
AakashM

65

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

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

从这个角度来看,MyClassRecipe<T>这不是一门课,而是一门课程的蓝图,是一本食谱。一旦用具体的东西(例如int,string等)代替T,就得到了一个类。在新创建的类中声明静态成员(字段,属性,方法)是完全合法的(就像在其他任何类中一样),并且此处没有任何错误的迹象。乍一看,这看起来有点可疑static MyStaticProperty<T> Property { get; set; }在课堂蓝图中进行,但这也是合法的。您的属性也将被参数化或模板化。

难怪在VB中会调用static shared。但是,在这种情况下,您应该意识到,此类“共享”成员仅在相同确切类的实例之间共享,而不在通过替换<T>其他对象而产生的不同类之间共享。


1
我认为C ++名称最清楚。在C ++中,它们称为模板,即模板,具体类的模板。
迈克尔·布朗

8

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

我以为我会添加一个示例,说明如何使用此功能,即抑制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不同从其他两个。

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


由于静态初始化的规则(在首次引用该类之前,不会调用静态初始化器),您可以放弃Getter中的检查,而只是在静态实例声明中对其进行初始化。
迈克尔·布朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.