컴파일러 모호한 호출 오류-Func <> 또는 작업이있는 익명 메서드 및 메서드 그룹


102

함수를 호출하는 데 익명 메서드 (또는 람다 구문)가 아닌 메서드 그룹 구문을 사용하려는 시나리오가 있습니다.

이 함수에는 두 개의 오버로드가 있습니다. 하나는를, 다른 하나 ActionFunc<string>.

익명 메서드 (또는 람다 구문)를 사용하여 두 개의 오버로드를 행복하게 호출 할 수 있지만 메서드 그룹 구문을 사용하면 모호한 호출 의 컴파일러 오류가 발생 합니다. Action또는 로 명시 적으로 캐스팅하여 해결할 수 Func<string>있지만 이것이 필요하다고 생각하지는 않습니다.

누구든지 명시 적 캐스트가 필요한 이유를 설명 할 수 있습니다.

아래 코드 샘플.

class Program
{
    static void Main(string[] args)
    {
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();

        // These both compile (lambda syntax)
        classWithDelegateMethods.Method(() => classWithSimpleMethods.GetString());
        classWithDelegateMethods.Method(() => classWithSimpleMethods.DoNothing());

        // These also compile (method group with explicit cast)
        classWithDelegateMethods.Method((Func<string>)classWithSimpleMethods.GetString);
        classWithDelegateMethods.Method((Action)classWithSimpleMethods.DoNothing);

        // These both error with "Ambiguous invocation" (method group)
        classWithDelegateMethods.Method(classWithSimpleMethods.GetString);
        classWithDelegateMethods.Method(classWithSimpleMethods.DoNothing);
    }
}

class ClassWithDelegateMethods
{
    public void Method(Func<string> func) { /* do something */ }
    public void Method(Action action) { /* do something */ }
}

class ClassWithSimpleMethods
{
    public string GetString() { return ""; }
    public void DoNothing() { }
}

C # 7.3 업데이트

로 당 0xcde 2019 년 3 월 20 일에 아래의 코멘트 (나는이 질문을 게시 구년 후!), C # 7.3 감사의로이 코드를 컴파일 개선 과부하 후보 .


코드를 시도했지만 추가 컴파일 시간 오류가 발생합니다. 'void test.ClassWithSimpleMethods.DoNothing ()'에 잘못된 반환 유형이 있습니다 (모호성 오류가있는 25 행에 있음)
Matt Ellen

@Matt : 그 오류도 봅니다. 내 게시물에서 인용 한 오류는 전체 컴파일을 시도하기 전에 VS가 강조하는 컴파일 문제였습니다.
Richard Ev

1
그건 그렇고, 이것은 좋은 질문이었습니다. 나는 어떤 사양 :에 힘 내게 아무것도 사랑
존 소총

1
향상된 오버로드 후보<LangVersion>7.3</LangVersion> 덕분에 C # 7.3 ( ) 이상 을 사용하는 경우 샘플 코드가 컴파일됩니다 .
0xced

답변:


97

먼저 Jon의 대답이 맞다고 말하겠습니다. 이것은 사양에서 가장 털이 많은 부분 중 하나이므로 Jon에게 먼저 뛰어 들었습니다.

둘째, 다음 줄을 말씀 드리겠습니다.

메서드 그룹에서 호환되는 대리자 형식으로 의 암시 적 변환이 있습니다.

(강조 추가됨) 깊이 오해의 소지가 있고 불행합니다. 여기서 "호환성"이라는 단어를 제거하는 것에 대해 Mads와 이야기를 나눌 것입니다.

이것이 오해의 소지가 있고 불행한 이유는 섹션 15.2, "호환성 위임"을 요구하는 것처럼 보이기 때문입니다. 15.2 절에서는 메소드와 델리게이트 유형 간의 호환성 관계를 설명 했지만 이는 메소드 그룹과 델리게이트 유형 의 변환 가능성에 대한 질문입니다 .

이제 6.6 섹션을 살펴보고 우리가 얻는 것을 볼 수 있습니다.

과부하 해결을 위해 먼저 어떤 과부하가 적용 가능한 후보 인지 결정해야합니다 . 모든 인수가 암시 적으로 형식 매개 변수 유형으로 변환 될 수있는 경우 후보가 적용 가능합니다. 프로그램의 단순화 된 버전을 고려하십시오.

class Program
{
    delegate void D1();
    delegate string D2();
    static string X() { return null; }
    static void Y(D1 d1) {}
    static void Y(D2 d2) {}
    static void Main()
    {
        Y(X);
    }
}

따라서 한 줄씩 살펴 보겠습니다.

메서드 그룹에서 호환되는 대리자 형식으로의 암시 적 변환이 있습니다.

나는 이미 여기서 "호환성"이라는 단어가 어떻게 불행한지 논의했습니다. 계속해. Y (X)에서 오버로드 해결을 수행 할 때 메서드 그룹 X가 D1로 변환되는지 궁금합니다. D2로 변환됩니까?

대리자 형식 D와 메서드 그룹으로 분류 된 식 E가 주어지면 E에 매개 변수를 사용하여 생성 된 인수 목록에 적용 가능한 [...] 메서드가 하나 이상 포함되어 있으면 E에서 D로 암시 적 변환이 존재합니다. 다음에 설명 된대로 D의 유형 및 수정 자.

여태까지는 그런대로 잘됐다. X에는 ​​D1 또는 D2의 인수 목록에 적용 할 수있는 메소드가 포함될 수 있습니다.

다음에서는 메서드 그룹 E에서 대리자 형식 D 로의 변환에 대한 컴파일 타임 응용 프로그램에 대해 설명합니다.

이 줄은 정말 흥미로운 것을 말하지 않습니다.

E에서 D 로의 암시 적 변환이 존재한다고해서 변환의 컴파일 타임 응용 프로그램이 오류없이 성공할 것이라는 보장은 없습니다.

이 라인은 매력적입니다. 암시 적 변환이 존재하지만 오류로 바뀔 수 있음을 의미합니다! 이것은 C #의 기괴한 규칙입니다. 잠시 벗어나기위한 예는 다음과 같습니다.

void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));

식 트리에서 증분 연산이 잘못되었습니다. 그러나, 람다는 여전히 컨버터블 변환이 이제까지 사용하는 경우에도 식 트리 유형, 에러입니다! 여기서 원칙은 나중에 표현식 트리에 들어갈 수있는 규칙을 변경하고자 할 수 있다는 것입니다. 이러한 규칙을 변경해 도 유형 시스템 규칙이 변경되어서는 안됩니다 . 우리는 당신의 프로그램이 명확하게하도록 강요 할 지금 우리가 더 잘 만들기 위해 미래에 식 트리에 대한 규칙을 변경할 때 그렇게하는 것이, 우리가 오버로드 확인에 깨는 변화를 소개하지 않습니다 .

어쨌든 이것은 이런 종류의 기괴한 규칙의 또 다른 예입니다. 과부하 해결을 위해 변환이 존재할 수 있지만 실제로 사용하기에는 오류가 있습니다. 사실 그것은 우리가 여기있는 상황과 정확히 일치하지 않습니다.

계속 진행 :

E (A) [...] 형식의 메서드 호출에 해당하는 단일 메서드 M이 선택됩니다. 인수 목록 A는 각 형식의 해당 매개 변수의 변수 [...]로 분류되는 표현식 목록입니다. -파라미터-리스트 D.

확인. 그래서 우리는 D1과 관련하여 X에서 과부하 해결을합니다. D1의 형식 매개 변수 목록이 비어 있으므로 X () 및 joy에 대한 과부하 해결을 수행하고 작동하는 "string X ()"메소드를 찾습니다. 마찬가지로 D2의 형식 매개 변수 목록은 비어 있습니다. 다시 말하지만, "string X ()"는 여기서도 작동하는 메서드입니다.

여기서 원칙은 메서드 그룹 변환 가능성을 결정하려면 오버로드 해결을 사용하여 메서드 그룹에서 메서드를 선택해야 하며 오버로드 해결은 반환 유형을 고려하지 않는다는 것 입니다.

알고리즘 [...]에서 오류가 발생하면 컴파일 타임 오류가 발생합니다. 그렇지 않으면 알고리즘은 D와 동일한 수의 매개 변수를 갖는 단일 최상의 방법 M을 생성하고 변환이 존재하는 것으로 간주됩니다.

방법 그룹 X에는 방법이 하나뿐이므로 최상의 방법이어야합니다. 우리는 X에서 D1로 그리고 X에서 D2 로의 변환 이 존재 한다는 것을 성공적으로 증명했습니다 .

자,이 줄이 관련이 있습니까?

선택한 메서드 M은 대리자 형식 D와 호환되어야합니다. 그렇지 않으면 컴파일 타임 오류가 발생합니다.

사실,이 프로그램에는 없습니다. 우리는이 라인을 활성화하는 데까지 도달하지 못합니다. 왜냐하면 우리가 여기서하는 것은 Y (X)에서 과부하 해결을 시도하는 것입니다. 두 개의 후보 Y (D1)와 Y (D2)가 있습니다. 둘 다 적용 가능합니다. 어느 것이 더 낫 습니까? 사양 어디에도 이러한 두 가지 가능한 변환 간의 더 나은 점을 설명하지 않습니다 .

이제, 유효한 변환이 오류를 생성하는 변환보다 낫다고 확실히 주장 할 수 있습니다. 이는이 경우에 과부하 해결이 우리가 피하고 싶은 반환 유형을 고려한다는 것을 효과적으로 말하는 것입니다. 질문은 어떤 원칙이 더 낫다는 것입니다. (1) 과부하 해결이 반환 유형을 고려하지 않는 불변성을 유지하거나 (2) 우리가 알지 못하는 변환을 선택하려고 노력합니까?

이것은 판단 호출입니다. 함께 람다 , 우리는 섹션 7.4.3.3에서, 변환의 이러한 종류의 반환 형식을 고려 :

E는 익명 함수이고 T1 및 T2는 동일한 매개 변수 목록이있는 대리자 형식 또는 식 트리 형식이며, 해당 매개 변수 목록의 컨텍스트에서 E에 대해 유추 된 반환 형식 X가 존재하며 다음 중 하나가 유지됩니다.

  • T1에는 반환 유형 Y1이 있고 T2에는 반환 유형 Y2가 있으며 X에서 Y1 로의 변환이 X에서 Y2 로의 변환보다 낫습니다.

  • T1에는 반환 유형 Y가 있고 T2는 무효 반환입니다.

이 점에서 메서드 그룹 변환과 람다 변환이 일치하지 않는 것은 유감입니다. 그러나 나는 그것으로 살 수 있습니다.

어쨌든, X에서 D1로 또는 X에서 D2로 더 나은 변환을 결정하는 "더 나은"규칙이 없습니다. 따라서 우리는 Y (X)의 해상도에 대한 모호성 오류를 제공합니다.


8
크래킹-답변과 (희망적으로) 그에 따른 사양 개선에 많은 감사를드립니다. 개인적으로 오버로드 해결이 메서드 그룹 변환대한 반환 유형을 고려 하여 동작을보다 직관적으로 만드는 것이 합리적이라고 생각 하지만 나는 그것이 일관성을 희생하면서 그렇게 할 것이라는 것을 이해합니다. (이전에 논의한 것처럼 메서드 그룹에 메서드가 하나만있을 때 메서드 그룹 변환에 적용되는 제네릭 유형 추론에 대해서도 마찬가지입니다.)
Jon Skeet

35

편집 : 나는 그것을 가지고 있다고 생각합니다.

zinglon이 말했듯 이 컴파일 타임 애플리케이션이 실패하더라도 에서 GetString로의 암시 적 변환이 있기 때문 Action입니다. 다음은 섹션 6.6에 대한 소개입니다.

메서드 그룹 (§7.1)에서 호환되는 대리자 형식으로의 암시 적 변환 (§6.1)이 있습니다. 대리자 형식 D와 메서드 그룹으로 분류 된 식 E가 지정된 경우 E에 생성 된 인수 목록에 대한 일반 형식 (§7.4.3.1)으로 적용 할 수있는 메서드가 하나 이상 포함되어 있으면 E에서 D로 암시 적 변환이 발생 합니다. 다음에 설명 된대로 D의 매개 변수 유형 및 수정자를 사용 합니다.

이제 호환되는 대리자 형식으로의 변환에 대해 말하는 첫 번째 문장이 혼란스러워졌습니다. 메서드 그룹의 Action메서드에 대해 호환되는 대리자가 GetString아니지만 GetString()메서드 D의 매개 변수 형식 및 수정자를 사용하여 생성 된 인수 목록에 일반 형식으로 적용 할 수 있습니다. 이것은 반환 형식에 대해 설명 하지 않습니다. D. 그것이 혼란스러워하는 이유입니다 . 변환을 적용GetString() 할 때 의 델리게이트 호환성 만 확인 하고 존재 여부를 확인하지 않기 때문입니다.

나는 방정식에서 오버로딩을 잠시 남겨두고 전환의 존재적용 가능성 사이의 이러한 차이가 어떻게 나타날 수 있는지 보는 것이 유익하다고 생각합니다 . 다음은 짧지 만 완전한 예입니다.

using System;

class Program
{
    static void ActionMethod(Action action) {}
    static void IntMethod(int x) {}

    static string GetString() { return ""; }

    static void Main(string[] args)
    {
        IntMethod(GetString);
        ActionMethod(GetString);
    }
}

Main컴파일 의 메서드 호출 식은 모두 아니지만 오류 메시지는 다릅니다. 다음은 다음과 같습니다 IntMethod(GetString).

Test.cs (12,9) : 오류 CS1502 : 'Program.IntMethod (int)'에 대한 최상의 오버로드 메서드 일치에 잘못된 인수가 있습니다.

즉, 사양의 섹션 7.4.3.1에서 적용 가능한 함수 멤버를 찾을 수 없습니다.

이제 다음과 같은 오류가 있습니다 ActionMethod(GetString).

Test.cs (13,22) : 오류 CS0407 : '문자열 Program.GetString ()'에 잘못된 반환 유형이 있습니다.

이번에는 호출하려는 메서드를 해결했지만 필요한 변환을 수행하지 못했습니다. 그것은 것 같습니다 - 불행하게도 나는 그 최종 점검이 수행되는 사양의 비트를 찾을 수 있습니다 7.5.5.1 될,하지만 난 정확한 위치를 볼 수 없습니다.


이 부분을 제외하고는 이전 답변이 제거되었습니다. 에릭이이 질문의 "이유"를 밝힐 수있을 것으로 기대하기 때문입니다.

아직 찾고 있습니다. 그동안 "Eric Lippert"라고 세 번 말하면 방문 할 수있을 것이라고 생각하십니까 (따라서 답변)?


@ 존 - 그것은 그 수 classWithSimpleMethods.GetStringclassWithSimpleMethods.DoNothing대표는 않나요?
Daniel A. White

@Daniel : 아니요-이러한 식은 메서드 그룹 식이며 오버로드 된 메서드는 메서드 그룹에서 관련 매개 변수 유형으로의 암시 적 변환이있는 경우에만 적용 가능한 것으로 간주되어야합니다. 사양의 섹션 7.4.3.1을 참조하십시오.
Jon Skeet

섹션 6.6을 읽으면 classWithSimpleMethods.GetString에서 Action으로의 변환이 존재하는 것으로 간주됩니다. 매개 변수 목록이 호환되기 때문에 존재하지만 변환 (시도한 경우)이 컴파일 시간에 실패합니다. 따라서 두 대리자 형식 모두에 대한 암시 적 변환 존재하며 호출이 모호합니다.
zinglon 2010 년

@zinglon : 당신은 어떻게 §6.6을 읽고는에서 변환 것을 결정하기 ClassWithSimpleMethods.GetString까지가 Action유효합니까? 방법은 내용 M델리게이트 타입과 호환 될 D(§15.2) "식별하거나 암시 기준 변환의 리턴 타입에서 존재 M의 리턴 타입 D."
jason

@Jason : 사양은 변환이 유효하다고 말하지 않고 존재 한다고 말합니다 . 사실 컴파일 타임에 실패하므로 유효하지 않습니다. §6.6의 처음 두 점은 변환이 존재하는지 여부를 결정합니다. 다음 사항은 변환의 성공 여부를 결정합니다. 포인트 2 : "그렇지 않으면 알고리즘은 D와 동일한 수의 매개 변수를 갖는 단일 최상의 방법 M을 생성하고 변환이 존재하는 것으로 간주됩니다." §15.2는 포인트 3에서 호출됩니다.
zinglon

1

사용 Func<string>Action<string>(분명히 매우 다르다 ActionFunc<string>에서) ClassWithDelegateMethods모호성 제거합니다.

모호성도 사이에서 발생 Action하고 Func<int>.

또한 이것으로 모호성 오류가 발생합니다.

class Program
{ 
    static void Main(string[] args) 
    { 
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods(); 
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods(); 

        classWithDelegateMethods.Method(classWithSimpleMethods.GetOne);
    } 
} 

class ClassWithDelegateMethods 
{ 
    public void Method(Func<int> func) { /* do something */ }
    public void Method(Func<string> func) { /* do something */ } 
}

class ClassWithSimpleMethods 
{ 
    public string GetString() { return ""; } 
    public int GetOne() { return 1; }
} 

추가 실험에 따르면 메서드 그룹을 자체적으로 전달할 때 사용할 오버로드를 결정할 때 반환 유형이 완전히 무시됩니다.

class Program
{
    static void Main(string[] args)
    {
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();

        //The call is ambiguous between the following methods or properties: 
        //'test.ClassWithDelegateMethods.Method(System.Func<int,int>)' 
        //and 'test.ClassWithDelegateMethods.Method(test.ClassWithDelegateMethods.aDelegate)'
        classWithDelegateMethods.Method(classWithSimpleMethods.GetX);
    }
}

class ClassWithDelegateMethods
{
    public delegate string aDelegate(int x);
    public void Method(Func<int> func) { /* do something */ }
    public void Method(Func<string> func) { /* do something */ }
    public void Method(Func<int, int> func) { /* do something */ }
    public void Method(Func<string, string> func) { /* do something */ }
    public void Method(aDelegate ad) { }
}

class ClassWithSimpleMethods
{
    public string GetString() { return ""; }
    public int GetOne() { return 1; }
    public string GetX(int x) { return x.ToString(); }
} 

0

와 과부하 FuncAction(둘 다 대표이기 때문에)에 가깝다

string Function() // Func<string>
{
}

void Function() // Action
{
}

알면 컴파일러는 반환 유형에 의해서만 다르기 때문에 호출 할 것을 알지 못합니다.


나는 그것이 정말로 그렇게 생각하지 않는다. 왜냐하면 a Func<string>Action...로 변환 할 수없고 문자열을 반환하는 메소드로만 구성된 메소드 그룹을 Action둘 중 하나로 변환 할 수 없기 때문이다 .
Jon Skeet

2
당신은 매개 변수 및 반환이없는 대리인 캐스트 할 수 string에를 Action. 왜 모호성이 있는지 모르겠습니다.
jason

3
@dtb : 예, 과부하를 제거하면 문제가 제거되지만 문제가있는 이유를 설명하지는 않습니다.
Jon Skeet
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.