제네릭 클래스의 정적 멤버가 특정 인스턴스에 연결되어 있습니까?


84

이것은 실제 질문보다 문서에 가깝습니다. 이것은 아직 (내가 놓치지 않는 한) 해결되지 않은 것 같습니다.

정적 멤버를 포함하는 제네릭 클래스를 상상해보십시오.

class Foo<T> {
    public static int member;
}

각 특정 클래스에 대한 멤버의 새 인스턴스가 있습니까? 아니면 모든 Foo 유형 클래스에 대해 단일 인스턴스 만 있습니까?

다음과 같은 코드로 쉽게 확인할 수 있습니다.

Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);

결과는 무엇이며이 동작은 어디에 문서화됩니까?


4
짧은 대답 : 각각의 실제 클래스 에 대해 새 인스턴스가 있습니다 . 즉, T사용 된 각 유형에 대해 하나씩 있습니다 ( Foo<int>그리고 Foo<string>두 개의 다른 클래스를 나타내며 각각 하나의 인스턴스를 가지지 만의 여러 인텐스는 Foo<int>의 단일 인스턴스를 공유합니다 member). 자세한 예는 다음을 참조하십시오. stackoverflow.com/a/38369256/336648
Kjartan

답변:


91

static필드는 모든 인스턴스에서 공유되는 동일한 유형의 . Foo<int>그리고 Foo<string>서로 다른 두 가지 종류가 있습니다. 이것은 다음 코드 줄로 증명할 수 있습니다.

// this prints "False"
Console.WriteLine(typeof(Foo<int>) == typeof(Foo<string>));

이것이 문서화 된 위치에 관해서 는 C # 언어 사양의 섹션 1.6.5 필드 (C # 3 용)에 다음이 있습니다.

정적 필드는 정확히 하나의 저장 위치를 ​​식별합니다. 생성 된 클래스의 인스턴스 수에 관계없이 정적 필드의 복사본은 하나뿐입니다.

앞서 언급했듯이 Foo<int>Foo<string>같은 클래스하지 않습니다; 동일한 일반 클래스로 구성된 두 개의 다른 클래스입니다. 어떻게 이런 일이 발생하는지 위에서 언급 한 문서의 섹션 4.4에 설명되어 있습니다.

제네릭 형식 선언은 그 자체로 형식 인수를 적용하여 다양한 형식을 형성하는 "청사진"으로 사용되는 바인딩되지 않은 제네릭 형식을 나타냅니다.


26
C #과 Java로 개발하는 경우 문제가 있습니다. 동안 Foo<int>Foo<String>C #의 다른 유형은, 그들은있는 같은 때문에 제네릭 방법 자바 거래 (유형 삭제 / 컴파일러 트릭)의 자바를 입력합니다.
Powerlord

만약 그렇다면 : Foo <int> foo1 = new Foo <int> (); foo1.member = 10; Foo <int> foo2 = 새로운 Foo <int> (); foo2.member = 20; 여기서 무슨 일이 일어나나요?
모두

@Everyone 멤버의 값은 동일한 유형 Foo <int>를 공유하기 때문에 두 인스턴스 모두에 대해 변경되며 20이됩니다.
스타스 이바노프

1
정적 멤버가있는 비 제네릭 기본 클래스를 파생 제네릭 클래스로 상속하면 어떻게됩니까? 이것이 여전히 사실일까요?
Brain2000

@StasIvanov, foo1.member는 10으로 유지되고 foo2.member는 20이됩니다. 확인하십시오
SHRI

16

여기서 문제는 실제로 "일반 클래스"가 전혀 클래스가 아니라는 사실입니다.

일반 클래스 정의는 클래스에 대한 템플릿 일 뿐이며 유형 매개 변수가 지정 될 때까지는 텍스트 조각 (또는 소수의 바이트) 일뿐입니다.

런타임에 템플릿에 대한 유형 매개 변수를 지정하여 활력을 불어 넣고 이제 완전히 지정된 유형의 클래스를 생성 할 수 있습니다. 이것이 정적 속성이 템플릿 전체에 적용되지 않는 이유 List<string>이며 및 사이에 캐스팅 할 수없는 이유 List<int>입니다.

그 관계는 클래스-객체 관계를 약간 반영합니다. 객체를 인스턴스화 할 때까지 클래스가 존재하지 않는 * 것처럼 템플릿을 기반으로 클래스를 만들 때까지 제네릭 클래스는 존재하지 않습니다.

추신 : 선언하는 것이 가능합니다.

class Foo<T> {
    public static T Member;
}

T는 전문화에 따라 다르기 때문에 정적 멤버를 공유 할 수 없다는 것이 다소 분명합니다.


4

그들은 공유되지 않습니다. 문서화 된 위치는 확실하지 않지만 분석 경고 CA1000 ( 제네릭 형식에 정적 멤버를 선언하지 마십시오 )은 코드를 더 복잡하게 만들 위험이 있으므로 이에 대해 경고합니다.


1
와, FX Cop에 대해 잘 알지 못하는 또 다른 규칙입니다.
Matthew Whited


3

제네릭의 C # 구현은 C ++에 더 가깝습니다. 이러한 언어 모두에서 MyClass<Foo>MyClass<Bar>자바 정적 멤버를 공유하지 않는하지만 그들은 않습니다. C # 및 C ++ MyClass<Foo>에서는 제네릭이 일종의 매크로 인 것처럼 컴파일 타임에 내부적으로 완전히 새로운 형식을 만듭니다. 당신은 일반적으로, 스택 추적에서 자신의 생성 된 이름을 볼 수와 같은 수 MyClass'1MyClass'2. 이것이 그들이 정적 변수를 공유하지 않는 이유입니다. Java에서 제네릭은 제네릭이 아닌 유형을 사용하여 코드를 생성하고 전체에 유형 캐스트를 추가하는보다 간단한 컴파일러 방법으로 구현됩니다. 그래서 MyClass<Foo>MyClass<Bar>대신들이 모두 같은 클래스이며, 자바에서 두 개의 완전히 새로운 클래스를 생성하지 않습니다 MyClass아래 그들은 정적 변수를 공유하는 이유입니다.


1

그들은 실제로 공유되지 않습니다. 구성원이 인스턴스에 전혀 속하지 않기 때문입니다. 정적 클래스 멤버는 클래스 자체에 속합니다. 따라서 MyClass.Number가있는 경우 모든 MyClass.Number 개체에 대해 동일합니다. 이는 개체에 종속되지 않기 때문입니다. 개체없이 MyClass.Number를 호출하거나 수정할 수도 있습니다.

그러나 Foo <int>는 Foo <string>과 같은 클래스가 아니기 때문에이 두 숫자는 공유되지 않습니다.

이를 보여주는 예 :

TestClass<string>.Number = 5;
TestClass<int>.Number = 3;

Console.WriteLine(TestClass<string>.Number);  //prints 5
Console.WriteLine(TestClass<int>.Number);     //prints 3

-4

IMO, 테스트해야하지만

Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);

1컴파일하는 동안 컴파일러가 사용하는 모든 일반 클래스에 대해 하나의 클래스를 생성한다고 생각하기 때문에 출력됩니다 (예 : Foo<int>Foo<string>).

그러나 나는 100 % 확실하지 않다 =).

비고 : 이런 종류의 정적 속성을 사용하는 것은 좋은 디자인도 아니고 좋은 습관도 아니라고 생각합니다.


6
확실하지 않은 100 % 경우 -하지 코멘트 할
SLL
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.