정적 메서드가 인스턴스 메서드를 호출하는 경우 C # 컴파일러가 오류 코드가 아닌 이유는 무엇입니까?


110

다음 코드에는 Foo()인스턴스 메서드를 호출하는 정적 메서드가 있습니다 Bar().

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

오류 *없이 컴파일되지만 런타임에 런타임 바인더 예외가 생성됩니다. 이러한 메서드에 대한 동적 매개 변수를 제거하면 예상대로 컴파일러 오류가 발생합니다.

그렇다면 왜 동적 매개 변수가 있으면 코드를 컴파일 할 수 있습니까? ReSharper도 오류로 표시하지 않습니다.

편집 1 : * Visual Studio 2008에서

편집 2 :sealed 하위 클래스에 정적 Bar(...)메서드 가 포함될 수 있기 때문에 추가되었습니다 . 봉인 된 버전도 런타임에 인스턴스 메서드 이외의 메서드를 호출 할 수없는 경우 컴파일됩니다.


8
+1 아주 좋은 질문
cuongle

40
이것은 Eric-Lippert 질문입니다.
Olivier Jacot-

3
나는 Jon Skeet이 이것으로 무엇을해야할지 알 것이라고 확신합니다.) @ OlivierJacot-Descombes
Thousand

2
컴파일러는 그것을 할 수 있도록 @Olivier, 존 소총은 아마 컴파일에 코드를 원했다 :-))
마이크 스콧

5
이것은 dynamic정말로 필요하지 않는 한 사용 하지 말아야하는 또 다른 예입니다 .
Servy

답변:


71

업데이트 : 아래 답변은 C # 7.3 (2018 년 5 월)이 도입되기 전인 2012 년에 작성되었습니다 . 에서 C # 7.3의 새로운 기능개선 과부하 후보 , 항목 1, 오버로드 확인 규칙은 비 정적 과부하 일찍 폐기되도록 변경하는 방법을 설명합니다. 따라서 아래 답변 (및이 전체 질문)은 지금까지 대부분 역사적인 관심을 가지고 있습니다!


(C # 7.3 이전 :)

어떤 이유로 과부하 해결 은 정적 대 비정 적을 확인 하기 전에 항상 최상의 일치 찾습니다 . 모든 정적 유형으로이 코드를 시도하십시오.

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

최상의 오버로드는 string. 그러나 이것은 인스턴스 메소드이므로 컴파일러가 불평합니다 (두 번째로 좋은 오버로드를 취하는 대신).

덧셈 : 그래서 dynamic원래 질문 의 예에 대한 설명은 일관성을 유지하기 위해 유형이 동적 일 때 먼저 최상의 과부하를 찾는 것입니다 (정적 대 비가 아닌 매개 변수 번호 및 매개 변수 유형 등 만 확인). - 정적 ), 그런 다음 정전기 확인하십시오. 그러나 이는 정적 검사가 런타임까지 기다려야 함을 의미합니다. 따라서 관찰 된 행동.

후기 추가 :이 재미있는 순서를 선택한 이유에 대한 배경 정보는 Eric Lippert의 블로그 게시물 에서 추론 할 수 있습니다 .


원래 질문에는 과부하가 없습니다. 정적 과부하를 보여주는 답변은 관련이 없습니다. 나는 그것을 기록하지 않았기 때문에 그것은 :-) "... 당신이 쓴 경우도"대답을 잘못입니다
마이크 스콧

5
@MikeScott 저는 C #의 오버로드 해결이 항상 다음과 같이 진행된다는 것을 확신 시키려고합니다. (1) 정적 / 비정 적을 무시하고 최상의 일치를 찾습니다. (2) 이제 우리는 사용에 어떤 과부하, 알고 다음 정적을 확인합니다. 이 때문에 dynamic언어로 소개 되었을 때 C #의 디자이너들은 " dynamic표현식 일 때 (2) 컴파일 시간을 고려하지 않을 것"이라고 말했습니다 . 그래서 여기서 제 목적은 런타임까지 정적 대 인스턴스를 확인하지 않기로 선택한 이유를 생각하는 것입니다. 이 검사는 binding-time 에서 발생합니다 .
Jeppe Stig Nielsen

충분히 공평하지만이 경우 컴파일러가 인스턴스 메서드에 대한 호출을 확인할 수없는 이유를 설명하지 않습니다. 즉, 컴파일러가 해결을 수행하는 방식은 단순합니다. 내 예제와 같이 호출을 해결할 수 없을 가능성이없는 간단한 경우를 인식하지 못합니다. 아이러니는 동적 매개 변수가있는 단일 Bar () 메서드를 사용하여 컴파일러가 해당 단일 Bar () 메서드를 무시한다는 것입니다.
Mike Scott

45
저는 C # 컴파일러의이 부분을 작성했으며 Jeppe가 옳습니다. 투표 해주세요. 오버로드 해결은 주어진 메서드가 정적인지 인스턴스 메서드인지 확인하기 전에 발생하며,이 경우에는 런타임에 대한 오버로드 해결을 연기하므로 런타임까지 정적 / 인스턴스 검사도 연기합니다. 또한 컴파일러는 절대적으로 포괄적이지 않은 동적 오류를 정적으로 찾기 위해 "최선의 노력"을 기울입니다.
Chris Burrows 2012 년

30

Foo에는 동적 인 매개 변수 "x"가 있습니다. 이는 Bar (x)가 동적 표현식임을 의미합니다.

Example은 다음과 같은 메소드를 가질 수 있습니다.

static Bar(SomeType obj)

이 경우 올바른 메서드가 해결되므로 Bar (x) 문은 완벽하게 유효합니다. 인스턴스 메서드 바 (x는)이 있다는 사실도 고려 의미도 없습니다 및되지 않습니다 : 정의 바 (X)는 동적 인 표현이기 때문에, 우리는 런타임에 해상도를 연기했다.


14
하지만 인스턴스 Bar 메서드를 꺼내면 더 이상 컴파일되지 않습니다.
Justin Harvey

1
@Justin 흥미로운-경고? 아니면 오류? 어느 쪽이든 메서드 그룹까지만 유효성을 검사하여 전체 오버로드 해결을 런타임에 남겨 둘 수 있습니다.
Marc Gravell

1
@Marc, 다른 Bar () 메서드가 없기 때문에 질문에 대답하지 않습니다. 오버로드가없는 Bar () 메서드가 하나뿐이라는 점을 감안할 때 이것을 설명 할 수 있습니까? 다른 메서드를 호출 할 수 없는데 왜 런타임을 연기합니까? 아니면 거기 있습니까? 참고 : 여전히 컴파일되는 클래스를 봉인하기 위해 코드를 편집했습니다.
마이크 스콧

1
@mike, 왜 런타임을 연기하는지 : 그것이 동적 의미
Marc Gravell

2
@Mike 불가능은 요점이 아닙니다. 중요한 것은 그것이 필요한지 입니다. dynamic의 요점은 컴파일러의 일이 아니라는 것입니다.
Marc Gravell

9

"동적"표현식은 런타임 중에 바인딩되므로 올바른 서명 또는 인스턴스 메서드를 사용하여 정적 메서드를 정의하면 컴파일러가이를 확인하지 않습니다.

"올바른"방법은 런타임 중에 결정됩니다. 컴파일러는 런타임 동안 유효한 메서드가 있는지 알 수 없습니다.

"dynamic"키워드는 동적 및 스크립트 언어에 대해 정의되며 런타임 중에도 언제든지 Method를 정의 할 수 있습니다. 미친 물건

여기에 메서드가 인스턴스에 있기 때문에 int를 처리하지만 문자열은 처리하지 않는 샘플이 있습니다.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

처리 할 수없는 모든 "잘못된"호출을 처리하는 메서드를 추가 할 수 있습니다.

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

예제의 호출 코드가 Example.Foo (...) 대신 Example.Bar (...) 여야하지 않습니까? Foo ()가 귀하의 예와 관련이 없습니까? 나는 당신의 예를 정말로 이해하지 못합니다. 정적 제네릭 메서드를 추가하면 왜 문제가 발생합니까? 옵션으로 제공하는 대신 해당 방법을 포함하도록 답변을 편집 할 수 있습니까?
Mike Scott

그러나 내가 게시 한 예제에는 단일 인스턴스 메서드 만 있고 오버로드가 없으므로 컴파일 타임에 해결할 수있는 가능한 정적 메서드가 없음을 알 수 있습니다. 적어도 하나를 추가하는 경우에만 상황이 변경되고 코드가 유효합니다.
Mike Scott

그러나이 예제에는 여전히 둘 이상의 Bar () 메서드가 있습니다. 내 예에는 한 가지 방법 만 있습니다. 따라서 정적 Bar () 메서드를 호출 할 가능성이 없습니다. 호출은 컴파일 타임에 확인할 수 있습니다.
마이크 스콧

@Mike는 될 수 있습니다! = is; 동적에서는 그렇게 할 필요가 없습니다.
Marc Gravell

@MarcGravell, 명확히 해주세요?
Mike Scott
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.