2013-08-20 4 views
4

인터넷 및 stackoverflow에 대한 몇 가지 기본 검색을 수행했는데 일반 버전 메서드와 비 제너릭 버전 메서드가 관련되어있을 때 과부하 해결에 대해 많은 논의를 보았습니다. 나는 과부하 reslolution 컴파일 시간에 이루어집니다 이해 -이 코드가 있으므로 경우 "InternalDoStuff"의 해상도가 컴파일러와 어떤 컴파일러에 의해 정리되어 있기 때문에일반 및 비 제너릭 메서드 사이의 C# 오버로드 해결

public class A<T> 
{ 
    public void DoStuff(T value) 
    { 
     InternalDoStuff(value); 
    } 

    protected void InternalDoStuff(int value) 
    { 
     Console.WriteLine("Non-generic version"); 
    } 

    protected void InternalDoStuff(T value) 
    { 
     Console.WriteLine("Generic version"); 
    } 

} 

public class Test 
{ 
    static void Main (string [] args) 
    { 
     A<int> a = new A<int>(); 
     a.DoStuff(100); 
    } 
} 

가 출력 될 "일반 버전"됩니다 "InternalDoStuff는 DoStuff에서 T 유형 매개 변수로 호출됩니다."입니다.

public class B : A <int> 
{ 

} 

public class Test 
{ 
    static void Main (string [] args) 
    { 
     B b = new B(); 
     b.DoStuff(100); 
    } 
} 

지금 내가 그 컴파일러는 "B는 특정 버전"을 결정하기에 충분한 정보를 가지고 말할 수있는, 따라서 제네릭이 아닌 버전을 호출 :이 어떤 변화를 가져올 것인지 잘 모릅니다 그러나

InternalDoStuff?

이러한 종류의 과부하 해결을 분석하는 데 일반적인 원칙이 있습니까?

+2

첫 번째 질문 - "컴파일러에 비 제네릭 버전을 호출하기위한 충분한 정보가 있다고 할 수 있습니까?" - 스스로 시도하여 대답 할 수 있습니다. 두 번째 질문은 C# 사양의 오버로드 해결 섹션을 읽음으로써 답변을 얻을 수 있습니다. –

답변

3

두 번째 방법은 첫 번째 방법과 다른 점이 없습니다.

클래스 B를 A에서 파생시키는 것은 클래스 A에 대해 생성 된 IL 코드를 변경하지 않습니다. B는 단순히 이러한 메서드를 상속합니다. 당신은 클래스 A의 IL 코드를 보면

, 당신은 제네릭이 아닌 버전 대신 일반 버전을 호출하는 컴파일 된 것을 볼 수 있습니다 - 존 소총의 기사에서

.method public hidebysig instance void DoStuff(!T 'value') cil managed 
{ 
    .maxstack 8 
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldarg.1 
    L_0003: call instance void 
      ConsoleApplication1.A`1<!T>::InternalDoStuff(!0) <-- Generic version 
    L_0008: nop 
    L_0009: ret 
} 

here -

같은 이름이지만 서로 다른 서명을 가진 두 개의 메서드가있을 때 오버로드가 발생합니다. 컴파일시 컴파일러는 인수의 컴파일 시간 유형과 메소드 호출의 대상에 따라 호출 할 대상을 결정합니다. (여기서는 동적 인을 사용하지 않는다고 가정합니다. 이는 다소 복잡합니다. 입니다.)

런타임까지 동적 지연 해결 기능을 사용한다고 언급했듯이.

public void DoStuff(T value) 
{ 
    dynamic dynamicValue = value; 
    InternalDoStuff(dynamicValue); 
} 

Jon SkeetEric Lippert 상세하게 설명함으로써 여기에 대한 답변을 참조하십시오 -이 코드 조각은 당신의 접근 방식 모두 제네릭이 아닌 버전의 메서드를 호출합니다.

+3

"클래스 B를 A에서 파생하는 것은 절대로 클래스 B에 대해 생성 된 IL 코드를 변경하지 않습니다. B는 단순히 이러한 메소드를 상속합니다." - 이것은 납득이가는 지점입니다. – Xinchao

1

enter image description here 이것은 "비 일반"버전 호출합니다 :

public class A<T> 
{ 
    public virtual void DoStuff(T value) 
    { 
     InternalDoStuff(value); 
    } 

    protected void InternalDoStuff(int value) 
    { 
     Console.WriteLine("Non-generic version"); 
    } 

    protected void InternalDoStuff(T value) 
    { 
     Console.WriteLine("Generic version"); 
    } 

} 
public class B : A<int> 
{ 
    public override void DoStuff(int value) 
    { 
     InternalDoStuff(value); 
    } 
} 
+0

글쎄, 해봤 어. 두 경우 모두 출력은 "일반 버전"입니다. 나는 닷넷 4.5, 4.0, 3.5를 목표로 삼았다. 결과는 같습니다. (VS2012를 사용 중입니다.) – Xinchao

+0

@Matthew - 위 코드는 비 제네릭 버전을 인쇄합니다. Notice 알렉스는'DoStuff' 메소드를 오버 라이딩했습니다. –

+0

내 시스템의 출력 스크린 샷 (Net 4.5, 64 비트, 릴리스 빌드) – Alex

1

DoStuff의 내부 InternalDoStuff에 대한 호출이 시간 A<T>에 바인딩이 컴파일됩니다. 호출이 B의 인스턴스에서 발생한다는 사실은 어떤 식 으로든 과부하 해결에 영향을주지 않습니다. 포인트 DoStuff에서

  • InternalDoStuff(T value)
  • InternalDoStuff(int value)

에서 DoStuff 방법 '수 intT 값 때문에 과부하를 통과 선택할 2 InternalDoStuff 부재가있는 컴파일 일하지 마라. 따라서 적용 가능한 멤버는 InternalDoStuff(T)이고 컴파일러가이 것을 선택합니다.

+0

감사합니다. 그러나 Alex와 같은 결과를 제안하는 것처럼 들리지만 "비 제너릭"버전이 호출됩니다. 내가 맞습니까? 나는 그것을 시도하고 그 결과는 "Generic version"이다. 어떤 버전의 .Net 프레임 워크를 대상으로하는지는 중요하지 않습니다. 귀하의 의견에 대하여 "이전의 제네릭, 이제 완전히 인스턴스화 된", "완전히 인스턴스화 된"방법이란 정확히 무엇을 의미합니까? – Xinchao

+0

@Matthew blah, 원래 샘플을 잘못 읽었습니다. 'B'에 대한 전화가 'InternalDoStuff' – JaredPar

+0

인 줄 알았는데 요. InternalDoStuff 인 경우 비 제너릭 버전이 호출됩니다. 그러나 B가 A를 "구체적으로 또는 완전히 시작된"것으로 만들기 때문에 아마도 그렇지 않습니다. InternalDoStuff (T value) 대 InternalDoStuff (int value)의 해결 문제에 수렴합니다. InternalDoStuff를 public으로 만들고 A 에 직접 호출하면 그 결과는 동일하게됩니다 : 비 제네릭 버전은 – Xinchao

2

C++에서는 컴파일 타임에 프로그램이 생성 할 수있는 모든 유형을 생성해야합니다. C++ 템플릿은 C# generics와 유사하지만 대체 매크로는 대체 매크로와 유사합니다. 컴파일러는 제네릭 형식 대체로 인해 발생할 수있는 모든 클래스를 개별적으로 생성하기 때문에 각 클래스에 대해 별도로 오버로드 해결과 같은 항목을 평가할 수 있습니다. C# generics는 그렇게 작동하지 않습니다.

C# 코드 컴파일은 두 단계로 구분됩니다. 첫 번째 단계는 빌드시에 수행됩니다. 이 단계를 처리하는 컴파일러는 소스 코드를 가져 와서 "Common Intermediate Language"형식으로 변환합니다 (동일한 CIL 형식이 VB.NET, F # 등 - 따라서 이름에 사용됩니다). 소스 코드의 모든 일반 클래스 정의 (예 : List<T>)는 CIL 형식으로 하나의 클래스 정의를 생성합니다. 컴파일러는 CIL을 생성하기 전에 함수 오버로드가 적용될 것인지에 대한 모든 결정을 내립니다.

나중에 프로그램을 실행하면 Common Language Runtime이 프로그램에서 사용하는 모든 클래스에 대한 코드를 생성하지는 않지만 대신 실제로 사용될 때까지 각 클래스의 코드 생성을 지연합니다 . 이 단계에서 List<int>과 같은 것은 List<string> 또는 List<KeyValuePair<Dictionary<int,string>, Tuple<Cat,Dog>>>에서 다른 기계어 코드를 생성합니다. 프로그램이 사용하고자하는 가능한 유형 세트는 한정 될 필요가 없습니다. C#에서는 합법적으로 매개 변수 T을 사용하여 List<T>이라는 일반 메서드를 호출하는 메서드를 사용할 수 있습니다 (주어진이 List<List<T>>을 전달하고 주어진 경우 List<List<List<T>>> 등을 전달하는 메서드). 프로그램이 OutOfMemoryException 또는 이와 유사한 문제로 죽을 가능성이 높습니다. C++과 달리 프로그램이 일 수있는 경우 컴파일시에 제약이 없도록 수 있습니다. 프로그램이 실제로 너무 많은 다른 유형을 사용하려고 시도하는 경우에만 문제가 발생합니다.

CLR은 코드를 생성 할 때 몇 가지 유형의 일반 대체를 만들 수 있지만 과부하 확인을 처리하지 않습니다 (C#에서 CIL 로의 변환 단계에서 처리됨). CLR에서 과부하 해결과 같은 이점을 가질 수도 있지만 CLR을 훨씬 더 복잡하게 만듭니다. 특히 까다로운 과부하 해결 문제가 4 분의 1이 걸리면 C# 코드를 컴파일하는 데 문제가되지 않을 수도 있지만 그런 경우에는 4 분의 1 초 동안 런타임을 중지하는 것이 바람직하지 않습니다.

+0

C# 제네릭이나 매크로에 관해 설명하여 C++ 템플릿에 대한 정의를 확신 할 수 없습니다. 또한 C++ 템플릿은 C# 제네릭이 이미 오래전부터 존재했기 때문에 템플릿 및 차이점에 대해 C# 제네릭을 설명하는 것이 일반적이라고 생각합니다 (그러나이 문법은 제네릭을 잘 이해하는 사용자에게 더 적합 할 수 있지만이 질문은 아닙니다. –

+0

@BenVoigt : C++ 템플릿은 전 처리기 매크로를 훨씬 넘어서 의미 론적 수준에서 작동하지만, 요점은 템플릿'Foo '을 정의하고'Foo ','Foo ','Foo '이면 C++ 컴파일러는 이것을 3 개의 비 - 제네릭 클래스로 바꿀 것입니다. 대조적으로'Foo '이 C# 제네릭 클래스 인 경우 C# 컴파일러는 제네릭 클래스를 지정하는 CIL 코드 한 부를 출력합니다. – supercat

+0

예 동의합니다. C++ 컴파일러는 템플릿의 인스턴스화를 수행하고 C# 컴파일러는 제네릭을 인스턴스화하지 않습니다 (특수화는 제네릭의 기능이 아닙니다). CLR 수준에서도 유형 인수가없는 경우 제네릭을 인스턴스화하지 않습니다. 값 유형의 인스턴스화는 인라이닝을 허용하고 복싱을 피할 수 있지만 과부하 선택은 변경하지 않습니다. –