Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

C# 7.3 - 개선된 메서드 선택 규칙 3가지(Improved overload candidates)

C# 7.3 (1) - 개선된 문법 4개(Support == and != for tuples, Ref Reassignment, Constraints, Stackalloc initializers)
; https://www.sysnet.pe.kr/2/0/11552

C# 7.3 (2) - 개선된 메서드 선택 규칙 3가지(Improved overload candidates)
; https://www.sysnet.pe.kr/2/0/11553

C# 7.3 (3) - 자동 구현 속성에 특성 적용 가능(Attribute on backing field)
; https://www.sysnet.pe.kr/2/0/11554

C# 7.3 (4) - 사용자 정의 타입에 fixed 적용 가능(Custom fixed)
; https://www.sysnet.pe.kr/2/0/11555

C# 7.3 (5) - 구조체의 고정 크기를 갖는 fixed 배열 필드에 대한 직접 접근 가능(Indexing movable fixed buffers)
; https://www.sysnet.pe.kr/2/0/11556

C# 7.3 (6) - blittable 제네릭 제약(blittable)
; https://www.sysnet.pe.kr/2/0/11558

C# 7.3 (7) - 초기화 식에서 변수 사용 가능(expression variables in initializers)
; https://www.sysnet.pe.kr/2/0/11560




C# 6.0에서도 한 차례 개선이 있었지만 메서드 오버로드에 대한 좀 더 정확한 식별 작업이 C# 7.3에도 추가되었습니다.

하나씩 예를 들어볼까요? ^^

우선 정적/인스턴스 멤버의 구분입니다. 정적/인스턴스 메서드가 함께 정의되어 있을 때 C# 7.2 이전까지는 아래의 코드를 컴파일할 수 없었습니다.

class StaticButInstanceMatchFirst
{
    public static void Do(object obj)
    {
    }

    public void Do(string txt)
    {
    }
}

static void Main(string[] args)
{
    // C# 7.2까지 컴파일 에러 - Error CS0120 An object reference is required for the non-static field, method, or property 'StaticButInstanceMatchFirst.Do(string)'
    StaticButInstanceMatchFirst.Do("TEST"); // 우회적으로 (object)"TEST"로 인자를 전달하면 OK
}

왜냐하면, 시그니처가 일치하는 메서드가 우선순위가 높고 일단 매칭이 되면 더 이상의 검색을 하지 않기 때문입니다. 즉, 정적/비정적 호출과 관계없이 "void Do(string txt)"와 일치하는 메서드를 호출한 것으로 판단을 해버렸고 그에 대해 인스턴스 호출이 아니라는 규칙을 나중에 적용해 오류라고 판단하는 것입니다.

이와 반대의 경우도 있습니다.

class InstanceButStaticMatchFirst
{
    public static void Do(string txt)
    {
    }

    public void Do(object obj)
    {
    }
}

static void Main(string[] args)
{
    InstanceButStaticMatchFirst t = new InstanceButStaticMatchFirst();

    // C# 7.2까지 컴파일 에러 - Error CS0176 Member 'InstanceButStaticMatchFirst.Do(string)' cannot be accessed with an instance reference; qualify it with a type name instead
    t.Do("TEST");  // 우회적으로 (object)"TEST"로 인자를 전달하면 OK
}

이번 역시 마찬가지로 static/instance 모든 멤버를 대상으로 정확히 일치하는 시그니처를 가진 메서드(static void Do)가 검색되었지만 인스턴스 멤버가 아니므로 오류가 발생한 것입니다. 이에 대해, 단순하게 "클래스 이름"으로 시작하는 호출은 정적 메서드에서, 그렇지 않은 경우에는 인스턴스 메서드를 대상으로 검색하면 된다고 정할 수도 있습니다. 만약 그렇게 규칙을 정하면 (현재 잘 빌드가 되는) 다음의 코드에서 다시 문제가 발생합니다.

class A
{
    public static void M(string txt)
    {
        System.Console.WriteLine("static M(string)");
    }

    public void M(object obj)
    {
        System.Console.WriteLine("M(object)");
    }
}

class B
{
    public static void M(object obj)
    {
        System.Console.WriteLine("static M(object)");
    }

    public void M(string txt)
    {
        System.Console.WriteLine("M(string)");
    }
}

class C
{
    // 참고: the Color Color problem
    // https://blogs.msdn.microsoft.com/ericlippert/2009/07/06/color-color/

    public A A = new A(); // 클래스 명과 필드의 이름이 같음
    public B B = new B(); // 클래스 명과 필드의 이름이 같음

    public void N()
    {
        // 아래의 코드를 어떻게 처리하는지는 C# 3.0 문서의 7.5.4.1 절을 참고
        A.M("hello");
        B.M("hello");
    }
}

/*
출력 결과:

static M(string)
M(string)
*/

기존에 위의 메서드는 [A].M, [B].M 호출에서 [A], [B]가 클래스 이름인지, 인스턴스 변수 이름인지에 상관없이 대상 타입의 모든 메서드를 검색 대상으로 하기 때문에 적절한 선택을 할 수 있습니다.

이 모든 상황을 고려하기 위해 C# 7.3부터 도입한 규칙이 다음의 내용입니다.

1. When a method group contains both instance and static members, we discard the instance members if invoked without an instance receiver or context, and discard the static members if invoked with an instance receiver. When there is no receiver, we include only static members in a static context, otherwise both static and instance members. When the receiver is ambiguously an instance or type due to a color-color situation, we include both. A static context, where an implicit this instance receiver cannot be used, includes the body of members where no this is defined, such as static members, as well as places where this cannot be used, such as field initializers and constructor-initializers.

정리해 보면 다음과 같습니다.

1) "an instance receiver or context" 없이 호출되면 인스턴스 멤버를 제외
2) "an instance receiver"와 함께 호출되면 정적 멤버를 제외

3) receiver가 없으면,
    static 문맥인 경우 static 멤버만 포함
    그 외의 경우 변함없이 모든 멤버(instance/static) 포함

4) receiver가 모호한 경우 변함없이 모든 멤버(instance/static) 포함

1)번 규칙으로 인해 StaticButInstanceMatchFirst의 정적 Do 메서드의 호출이 정상적으로 컴파일됩니다.

static void Main(string[] args)
{
    // C# 7.3부터 정상 컴파일
    StaticButInstanceMatchFirst.Do("TEST");
}

마찬가지로 2)번 규칙으로 InstanceButStaticMatchFirst의 비정적 Do 메서드 호출이 잘 됩니다.

static void Main(string[] args)
{
    InstanceButStaticMatchFirst t = new InstanceButStaticMatchFirst();

    // C# 7.3부터 정상 컴파일
    t.Do("TEST");
}

3)번 규칙으로 인해 다음의 메서드 호출이,

class StaticButInstanceMatchFirst
{
    public static void Do(object obj)
    {
        System.Console.WriteLine("static Do(object)");
    }

    public void Do(string txt)
    {
        System.Console.WriteLine("Do(string)");
    }

    public static void StaticDone()
    {
        // C# 7.2까지 컴파일 오류 - Error CS0120 An object reference is required for the non-static field, method, or property 'StaticButInstanceMatchFirst.Do(string)'
        Do("test");
    }
}

C# 7.3부터는 receiver가 명시되지 않은 상태에서 "static" 문맥에서 호출되었으므로 대상이 static 멤버만 고려되므로 "public static void Do(object obj)" 메서드가 선택됩니다. 이로 인해 경우에 따라 receiver의 유무로 선택이 달라지는 실행이 있습니다. 가령 다음의 경우,

class InstanceButStaticMatchFirst
{
    public static void Do(string txt)
    {
        System.Console.WriteLine("static Do(string)");
    }

    public void Do(object obj)
    {
        System.Console.WriteLine("Do(object)");
    }

    public void InstanceDone()
    {
        Do("test"); // receiver 명시가 없으므로.
    }
}

InstanceDone에서 Do 메서드에 대한 receiver를 명시하지 않았고, static 문맥이 아니므로 모든 메서드를 대상으로 검색이 되므로 "static void Do" 메서드가 선택됩니다. 반면 receiver를 다음과 같이 명시하면,

public void InstanceDone()
{
    this.Do("test");
}

이제 2)번 규칙이 적용되므로 static 멤버가 제외되어 "public void Do(object obj)" 메서드가 선택됩니다.

마지막으로, 4)번 규칙으로 인해 위와 같은 규칙들을 세웠어도 "the Color Color problem" 예제도 컴파일이 잘 됩니다.




다음으로 개선된 것이 제네릭 메서드와의 모호함입니다. 설명을 위해 다음의 예제를 보면,

using System;
using System.Linq;
using System.Threading.Tasks;

public class AmbiguousMethods
{
    public static GenericResult<T> Do<T>(Func<T> func)
    {
        Console.WriteLine("GenericResult<T> Do");
        T inst = func();
        return new GenericResult<T>(inst);
    }

    public static int Do(Func<int> func)
    {
        Console.WriteLine("int Do");
        return func();
    }
}

public class GenericResult<T>
{
    T _inst;
    public GenericResult(T inst)
    {
        _inst = inst;
    }
}

class Program
{
    static void Main(string[] args)
    {
        AmbiguousMethods.Do(GetInteger);
    }

    private static int GetInteger()
    {
        return 5;
    }
}

AmbiguousMethods.Do 메서드 호출에서 int를 반환하는 메서드가 찾아지므로 당연히 public static int Do(Func<int> func)가 호출이 됩니다. 반면, 다음과 같이 호출하면 어떻게 될까요?

class Program
{
    static void Main(string[] args)
    {
        // C# 7.2까지 컴파일 오류 - Error CS0121 The call is ambiguous between the following methods or properties: 'AmbiguousMethods.Do<T>(Func<T>)' and 'AmbiguousMethods.Do(Func<GenericResult>)'
        AmbiguousMethods.Do(GetString);
    }

    private static string GetString()
    {
        return "test";
    }
}

일단, GetString은 int를 반환하지 않으므로 "public static int Do(Func<int> func)" 메서드와 맞지 않으므로 그다음 public static GenericResult<T> Do<T>(Func<T> func) 메서드를 선택해 빌드해야 합니다. 하지만 C# 7.2까지는 이런 경우 모호함 오류가 발생했고, 이를 보완하기 위해 다음과 같은 개선된 규칙이 C# 7.3부터 적용됩니다.

2. When a method group contains some generic methods whose type arguments do not satisfy their constraints, these members are removed from the candidate set.

이로 인해 혜택받는 코드가 바로 Task.Run입니다.

static void Main(string[] args)
{
    // C# 7.2까지 컴파일 오류 - Error CS0121 The call is ambiguous between the following methods or properties: 'Task.Run<TResult>(Func<TResult>)' and 'Task.Run(Func<Task>)'
    Task.Run(SumOfIntegers);
}

private static int SumOfIntegers()
{
    return Enumerable.Range(1, 1000).Sum();
}

C# 7.3부터는 위와 같은 코드가 정상적으로 컴파일 됩니다.




자, 이제 마지막 규칙입니다. ^^ C# 7.2까지 다음의 코드는 오류가 발생했습니다.

using System;

class Program
{
    public delegate int IntStringDelegate(string txt);
    public delegate string StringObjectDelegate(object obj);

    static void Main(string[] args)
    {
        // C# 7.2까지 컴파일 오류 - Error CS0121 The call is ambiguous between the following methods or properties: 'Program.Call(Program.IntStringDelegate)' and 'Program.Call(Program.StringObjectDelegate)'
        Call(RetIntArgObject);

        // C# 7.2까지 컴파일 오류 - Error CS0121 The call is ambiguous between the following methods or properties: 'Program.Call(Program.IntStringDelegate)' and 'Program.Call(Program.StringObjectDelegate)'
        Call(RetStringArgObject);
    }

    public static void Call(IntStringDelegate func)
    {
        Console.WriteLine("IntStringDelegate");
    }

    public static void Call(StringObjectDelegate func)
    {
        Console.WriteLine("StringObjectDelegate");
    }

    private static int RetIntArgObject(object txt)
    {
        return 5;
    }

    private static string RetStringArgObject(object txt)
    {
        return txt.ToString();
    }
}

이유는, Call 메서드의 인자로 전달한 메서드가 2개의 후보로 선정된 delegate 중에 "반환값을 제외하고" 인자 타입이 일치하는 것이 선택된 탓입니다. 이 문제를 피하기 위해 C# 7.3부터는 다음의 규칙이 적용됩니다.

3. For a method group conversion, candidate methods whose return type doesn't match up with the delegate's return type are removed from the set.

따라서 반환 타입이 다르다면 후보 선정에서 탈락하므로 Call(RetIntArgObject); 호출은 "public delegate int IntStringDelegate(string txt);" 만이 고려가 되어 string 인자는 object로 처리됩니다. 반면, Call(RetStringArgObject); 호출은 "public delegate string StringObjectDelegate(object obj);" 만이 후보로 선정되므로 역시 정상적으로 컴파일이 됩니다.




(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 6/25/2018 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 




1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...
NoWriterDateCnt.TitleFile(s)
12036정성태10/21/20192168.NET Framework: 866. C# - 고성능이 필요한 환경에서 GC가 발생하지 않는 네이티브 힙 사용파일 다운로드1
12035정성태10/13/20191107개발 환경 구성: 461. C# 8.0의 #nulable 관련 특성을 .NET Framework 프로젝트에서 사용하는 방법파일 다운로드1
12034정성태1/31/20201381개발 환경 구성: 460. .NET Core 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 [1]
12033정성태10/19/20202129개발 환경 구성: 459. .NET Framework 프로젝트에서 C# 8.0/9.0 컴파일러를 사용하는 방법
12032정성태11/25/20191345.NET Framework: 865. .NET Core 2.2/3.0 웹 프로젝트를 IIS에서 호스팅(Inproc, out-of-proc)하는 방법 - AspNetCoreModuleV2 소개
12031정성태10/7/2019782오류 유형: 569. Azure Site Extension 업그레이드 시 "System.IO.IOException: There is not enough space on the disk" 예외 발생
12030정성태11/12/20192736.NET Framework: 864. .NET Conf 2019 Korea - "닷넷 17년의 변화 정리 및 닷넷 코어 3.0" 발표 자료 [1]파일 다운로드1
12029정성태9/27/2019962제니퍼 .NET: 29. Jennifersoft provides a trial promotion on its APM solution such as JENNIFER, PHP, and .NET in 2019 and shares the examples of their application.
12028정성태9/26/20191106.NET Framework: 863. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상을 해결하기 위한 시도파일 다운로드1
12027정성태9/26/2019691오류 유형: 568. Consider app.config remapping of assembly "..." from Version "..." [...] to Version "..." [...] to solve conflict and get rid of warning.
12026정성태9/26/20191059.NET Framework: 862. C# - Active Directory의 LDAP 경로 및 정보 조회
12025정성태9/25/20191095제니퍼 .NET: 28. APM 솔루션 제니퍼, PHP, .NET 무료 사용 프로모션 2019 및 적용 사례 (8)
12024정성태9/20/20191246.NET Framework: 861. HttpClient와 HttpClientHandler의 관계
12023정성태9/19/20191265.NET Framework: 860. ServicePointManager.DefaultConnectionLimit와 HttpClient의 관계파일 다운로드1
12022정성태9/12/20192502개발 환경 구성: 458. C# 8.0 (Preview) 신규 문법을 위한 개발 환경 구성 [3]
12021정성태9/17/20207285도서: 시작하세요! C# 8.0 프로그래밍 [3]
12020정성태9/11/20191351VC++: 134. SYSTEMTIME 값 기준으로 특정 시간이 지났는지를 판단하는 함수
12019정성태9/11/2019973Linux: 23. .NET Core + 리눅스 환경에서 Environment.CurrentDirectory 접근 시 주의 사항
12018정성태9/25/2019859오류 유형: 567. IIS - Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive. (D:\lowSite4\web.config line 11)
12017정성태9/11/20191319오류 유형: 566. 비주얼 스튜디오 - Failed to register URL "http://localhost:6879/" for site "..." application "/". Error description: Access is denied. (0x80070005)
12016정성태9/5/20192129오류 유형: 565. git fetch - warning: 'C:\ProgramData/Git/config' has a dubious owner: '(unknown)'.
12015정성태9/3/20193074개발 환경 구성: 457. 윈도우 응용 프로그램의 Socket 연결 시 time-out 시간 제어
12014정성태10/28/20201686개발 환경 구성: 456. 명령행에서 AWS, Azure 등의 원격 저장소에 파일 관리하는 방법 - cyberduck/duck 소개
12013정성태8/28/20191598개발 환경 구성: 455. 윈도우에서 (테스트) 인증서 파일 만드는 방법
12012정성태8/28/20192338.NET Framework: 859. C# - HttpListener를 이용한 HTTPS 통신 방법
12011정성태8/27/20191717사물인터넷: 57. C# - Rapsberry Pi Zero W와 PC 간 Bluetooth 통신 예제 코드파일 다운로드1
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...