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 readonlyEnumRouteConstraint<T>있는 각각의 필드를 가지고 있다고 가정 합니다.


때로는 그 특징, 때로는 성가심. C #에서 키워드를 구분하기를 원했습니다
nawfal

답변:


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>는 제네릭이 아닌 기본 클래스에 포함되어 있으면 동일한 정적 값을 갖습니다.
bikeman868을

147

로부터 JetBrains의 위키 :

대부분의 경우 제네릭 형식의 정적 필드를 갖는 것은 오류의 신호입니다. 그 이유는 제네릭 형식의 정적 필드 가 다른 밀접하게 구성된 형식의 인스턴스간에 공유 되지 않기 때문입니다. 일반적인 클래스한다는 수단이 C<T>정적 필드가 X상기의 값 C<int>.X과는 C<string>.X 전혀 다른, 독립된 값을 갖는다.

드문 경우이지만 '특수화 된'정적 필드 필요한 경우 경고를 자유롭게 억제하십시오.

제네릭 인수가 다른 인스턴스간에 정적 필드를 공유해야하는 경우 제네릭이 아닌 기본 클래스를 정의 하여 정적 멤버를 저장 한 다음 제네릭 형식을이 형식에서 상속하도록 설정하십시오.


13
제네릭 형식을 사용하면 기술적으로 호스팅하는 각 제네릭 형식에 대해 별도의 별도 클래스가 생깁니다. 제네릭이 아닌 두 개의 개별 클래스를 선언 할 때 정적 변수를 공유하지 않을 것으로 예상되므로 제네릭이 다른 이유는 무엇입니까? 이것이 드문 것으로 간주 될 수있는 유일한 방법은 대부분의 개발자가 일반 클래스를 만들 때 수행하는 작업을 이해하지 못하는 경우입니다.
Syndog

2
@ Syndog 제네릭 클래스 내에서 설명 된 정적 동작은 나에게 잘 보이고 이해할 수 있습니다. 그러나 이러한 경고의 원인은 모든 팀이 개발자 만 경험하고 집중 한 것은 아니기 때문입니다. 개발자의 자격으로 인해 올바른 코드가 오류가 발생하기 쉽습니다.
Stas Ivanov

그러나이 정적 필드를 유지하기 위해 제네릭이 아닌 기본 클래스를 만들고 싶지 않으면 어떻게해야합니까? 이 경우 경고를 표시하지 않을 수 있습니까?
Tom Lint

@TomLint 당신이하고있는 일을 알고 있다면 경고를 억제하는 것이 실제로해야 할 일입니다.
AakashM

65

이것은 반드시 오류는 아닙니다 . C # 제네릭 에 대한 잠재적 오해 에 대해 경고합니다 .

제네릭의 기능을 기억하는 가장 쉬운 방법은 다음과 같습니다. 제네릭은 클래스를 만들기위한 "청사진"입니다. 클래스는 객체를 만들기위한 "청사진"입니다. (그러나 이것은 단순화입니다. 메소드 제네릭도 사용할 수 있습니다.)

이 관점 MyClassRecipe<T>에서 수업은 수업이 아닙니다. 수업 의 모습을 보여주는 레시피, 청사진입니다. int, string 등과 같은 구체적인 무언가로 T를 대체하면 클래스가 생깁니다. 새로 생성 된 클래스 (다른 클래스와 마찬가지로)에서 정적 멤버 (필드, 속성, 메서드)를 선언하고 여기에 오류가없는 것은 완벽하게 합법적입니다. static MyStaticProperty<T> Property { get; set; }클래스 청사진 내에서 선언하면 언뜻보기에는 다소 의심 스럽지만 합법적입니다. 귀하의 재산은 매개 변수화되거나 템플릿 화됩니다.

VB 스태틱에서 호출되는 것은 놀라운 일이 아닙니다 shared. 그러나이 경우 이러한 "공유"멤버는 동일한 클래스의 인스턴스간에 만 공유되며 다른 클래스로 대체 <T>하여 생성 된 고유 클래스 간에는 공유되지 않습니다 .


1
C ++ 이름이 가장 명확하다고 생각합니다. C ++에서는이를 구체적인 클래스 용 템플릿 인 템플릿이라고합니다.
Michael Brown

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.

thirdInst그리고 fourthInst다른 클래스 (의 인스턴스이다 SerializableEntity<OtherEntity>), 그리고 이렇게 인스턴스를 공유 _typeSpecificSerializer다른 다른 두에서.

즉, 각 엔터티 유형 마다 서로 다른 serializer 인스턴스를 얻는 동시에 각 실제 유형의 컨텍스트 내에서 정적 상태를 유지합니다 (예 : 특정 유형의 인스턴스간에 공유).


정적 초기화 규칙 (클래스가 처음 참조 될 때까지 정적 초기화 프로그램이 호출되지 않음)으로 인해 Getter에서 확인을 취소하고 정적 인스턴스 선언에서 초기화 할 수 있습니다.
Michael Brown
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.