Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 6개 있습니다.)

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 at outlook.com

비밀번호

댓글 작성자
 




... 31  32  33  [34]  35  36  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12770정성태8/11/20218416.NET Framework: 1086. C# - Windows Forms 응용 프로그램의 자식 컨트롤 부하파일 다운로드1
12769정성태8/11/20216406오류 유형: 752. Python - ImportError: No module named pip._internal.cli.main 두 번째 이야기
12768정성태8/10/20217427.NET Framework: 1085. .NET 6에 포함된 신규 BCL API [1]파일 다운로드1
12767정성태8/10/20218475오류 유형: 752. Python - ImportError: No module named pip._internal.cli.main
12766정성태8/9/20217011Java: 32. closing inbound before receiving peer's close_notify
12765정성태8/9/20216328Java: 31. Cannot load JDBC driver class 'org.mysql.jdbc.Driver'
12764정성태8/9/202144789Java: 30. XML document from ServletContext resource [/WEB-INF/applicationContext.xml] is invalid
12763정성태8/9/20217786Java: 29. java.lang.NullPointerException - com.mysql.jdbc.ConnectionImpl.getServerCharset
12762정성태8/8/202111308Java: 28. IntelliJ - Unable to open debugger port 오류
12761정성태8/8/20218526Java: 27. IntelliJ - java: package javax.inject does not exist [2]
12760정성태8/8/20215941개발 환경 구성: 594. 전용 "Command Prompt for ..." 단축 아이콘 만들기
12759정성태8/8/20219073Java: 26. IntelliJ + Spring Framework + 새로운 Controller 추가 [2]파일 다운로드1
12758정성태8/7/20218413오류 유형: 751. Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
12757정성태8/7/20219099Java: 25. IntelliJ + Spring Framework 프로젝트 생성
12756정성태8/6/20217918.NET Framework: 1084. C# - .NET Core Web API 단위 테스트 방법 [1]파일 다운로드1
12755정성태8/5/20217053개발 환경 구성: 593. MSTest - 단위 테스트에 static/instance 유형의 private 멤버 접근 방법파일 다운로드1
12754정성태8/5/20217988오류 유형: 750. manage.py - Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
12753정성태8/5/20218200오류 유형: 749. PyCharm - Error: Django is not importable in this environment
12752정성태8/4/20216311개발 환경 구성: 592. JetBrains의 IDE(예를 들어, PyCharm)에서 Visual Studio 키보드 매핑 적용
12751정성태8/4/20219347개발 환경 구성: 591. Windows 10 WSL2 환경에서 docker-compose 빌드하는 방법
12750정성태8/3/20216151디버깅 기술: 181. windbg - 콜 스택의 "Call Site" 오프셋 값이 가리키는 위치
12749정성태8/2/20215572개발 환경 구성: 590. Visual Studio 2017부터 단위 테스트에 DataRow 특성 지원
12748정성태8/2/20216185개발 환경 구성: 589. Azure Active Directory - tenant의 관리자(admin) 계정 로그인 방법
12747정성태8/1/20216785오류 유형: 748. 오류 기록 - MICROSOFT GRAPH – HOW TO IMPLEMENT IAUTHENTICATIONPROVIDER파일 다운로드1
12746정성태7/31/20218767개발 환경 구성: 588. 네트워크 장비 환경을 시뮬레이션하는 Packet Tracer 프로그램 소개
12745정성태7/31/20216621개발 환경 구성: 587. Azure Active Directory - tenant의 관리자 계정 로그인 방법
... 31  32  33  [34]  35  36  37  38  39  40  41  42  43  44  45  ...