developer tip

마지막 호출이 조건부 일 때 C # 컴파일러가 메서드 호출 체인을 제거하는 이유는 무엇입니까?

copycodes 2020. 11. 5. 08:14
반응형

마지막 호출이 조건부 일 때 C # 컴파일러가 메서드 호출 체인을 제거하는 이유는 무엇입니까?


다음 클래스를 고려하십시오.

public class A {
    public B GetB() {
        Console.WriteLine("GetB");
        return new B();
    }
}

public class B {
    [System.Diagnostics.Conditional("DEBUG")]
    public void Hello() {
        Console.WriteLine("Hello");
    }
}

이제 다음과 같이 메소드를 호출하면 :

var a = new A();
var b = a.GetB();
b.Hello();

릴리스 빌드 (즉, DEBUG플래그 없음 )에서는에 GetB대한 호출 Hello()이 컴파일러에 의해 생략 되므로 콘솔 에만 인쇄 된 것을 수 있습니다. 디버그 빌드에서는 두 인쇄가 모두 나타납니다.

이제 메서드 호출을 연결해 보겠습니다.

a.GetB().Hello();

디버그 빌드의 동작은 변경되지 않습니다. 그러나 플래그가 설정되지 않은 경우 다른 결과를 얻습니다. 호출이 모두 생략되고 콘솔에 인쇄가 나타나지 않습니다. IL을 간단히 살펴보면 전체 라인이 컴파일되지 않았 음을 알 수 있습니다.

C #최신 ECMA 표준 (ECMA-334, 즉 C # 5.0)에 따르면 Conditional속성이 메서드에 배치 될 때 예상되는 동작 은 다음과 같습니다 (강조 표시).

관련 조건부 컴파일 기호 중 하나 이상이 호출 지점에 정의 된 경우 조건부 메서드에 대한 호출이 포함되고, 그렇지 않으면 호출이 생략 됩니다. (§22.5.3)

이것은 전체 체인을 무시해야 함을 나타내는 것 같지 않으므로 내 질문입니다. 즉, MicrosoftC # 6.0 초안 사양 은 좀 더 자세한 내용을 제공합니다.

기호가 정의 된 경우 호출이 포함됩니다. 그렇지 않으면 호출 (수신자 평가 및 호출 매개 변수 포함)이 생략됩니다.

호출의 매개 변수가 평가되지 않는다는 사실은 사람들이 #if함수 본문에서 지시문 이 아닌이 기능을 사용하는 이유 중 하나이기 때문에 잘 문서화되어 있습니다 . 그러나 "수신자 평가"에 대한 부분은 새로운 것입니다. 다른 곳에서는 찾을 수없는 것 같고 위의 동작을 설명하는 것 같습니다.

이것에 비추어 내 질문은 : 이 상황에서 평가하지 않는 C # 컴파일러의 근거 무엇 a.GetB() 입니까? 조건부 호출의 수신자가 임시 변수에 저장되었는지 여부에 따라 실제로 다르게 동작해야합니까?


몇 가지 파고 들었고 C # 5.0 언어 사양 에는 실제로 섹션 17.4.2 The Conditional attribute on page 424 에서 두 번째 인용문이 이미 포함되어 있음을 발견했습니다 .

Marc Gravell의 답변은 이미이 동작이 의도 된 것이며 실제로 의미하는 바를 보여줍니다. 당신은 또한 이것 근거 에 대해 물 었지만 Marc의 오버 헤드 제거에 대한 언급에 불만족스러워 보입니다.

제거 할 수있는 오버 헤드가 고려 되는지 궁금 하십니까?

a.GetB().Hello();시나리오에서 전혀 호출되지 않고 Hello()생략 된 상태로 액면가에서 이상하게 보일 수 있습니다.

나는 결정의 근거는 모르지만 내 자신의 그럴듯한 추론을 발견했습니다. 아마도 그것은 당신에게도 도움이 될 수 있습니다.

메서드 체이닝 은 각 이전 메서드에 반환 값이있는 경우에만 가능합니다. 이것은 이러한 값으로 무언가를하고 싶을 때 의미가 있습니다.a.GetFoos().MakeBars().AnnounceBars();

값을 반환 하지 않고 무언가를 수행 하는 함수 가있는 경우 반환 유형이 void 여야하므로 조건부 메서드의 경우처럼 그 뒤에 무언가를 연결할 수는 없지만 메서드 체인의 끝에 넣을 수 있습니다.

또한 이전 메서드 호출 결과버려 지므로 귀하의 예제에서는 이 문이 실행 된 후 a.GetB().Hello();결과 GetB()가 존재할 이유가 없습니다. 기본적으로을 사용 하는 데만 결과가 필요함 의미 합니다 .GetB()Hello()

경우 Hello()당신이 필요 왜 생략 GetB()다음? Hello()을 생략 a.GetB();하면 할당이없고 많은 도구가 반환 값을 사용하지 않는다는 경고를 표시합니다. 이는 거의 수행 할 작업이 아니기 때문입니다.

당신이 이것에 대해 괜찮지 않은 것처럼 보이는 이유는 당신의 방법이 특정 값을 반환하는 데 필요한 것을 시도 할뿐만 아니라 I / O와 같은 부작용 도 가지고 있기 때문 입니다. 당신이 대신해야 않은 경우 순수 기능을 이 것이 정말 아무런 이유가 없다 GetB()당신이 결과에 아무것도하지 않을 경우 후속 호출, 즉 생략합니다.

의 결과를 GetB()변수에 할당하면 이것은 자체적으로 명령문이며 어쨌든 실행됩니다. 그래서이 추론은

var b = a.GetB();
b.Hello();

Hello()메소드 체인을 사용할 때 전체 체인이 생략되는 동안 호출 만 생략됩니다.

더 나은 관점을 얻기 위해 완전히 다른 곳을 볼 수도 있습니다. null 조건부 연산자 또는 C # 6.0에 도입 된 elvis 연산자 ? 입니다. null 검사를 사용하는 더 복잡한 표현식에 대한 구문 설탕 일 뿐이지 만 null 검사를 기반으로 단락 옵션이있는 메서드 체인과 같은 것을 만들 수 있습니다.

예를 들어 GetFoos()?.MakeBars()?.AnnounceBars();이전 메서드가을 반환하지 않는 경우에만 끝에 도달하고 null그렇지 않으면 후속 호출이 생략됩니다.

반 직관적 일 수 있지만 시나리오를 이와 반대로 생각해보십시오 . 어쨌든 체인의 끝에 도달하지 않았으므로 컴파일러 는 체인 Hello()에서 이전에 호출을 생략합니다 a.GetB().Hello();.


부인 성명

이것은 모두 안락 의자 추론 이니, 이것과 엘비스 오퍼레이터의 비유를 소금 한 알을 가지고 가져 가십시오.


다음과 같은 문구로 귀결됩니다.

(수신자 평가 및 호출 매개 변수 포함)은 생략됩니다.

표현에서 :

a.GetB().Hello();

"수신자 평가"는 a.GetB(). 따라서 사양에 따라 생략 되며 사용되지 않는 항목에[Conditional] 대한 오버 헤드를 피할 수있는 유용한 트릭 입니다. 로컬에 넣을 때 :

var b = a.GetB();
b.Hello();

then the "evaluation of the receiver" is just the local b, but the original var b = a.GetB(); is still evaluated (even if the local b ends up getting removed).

This can have unintended consequences, so: use [Conditional] with great care. But the reasons are so that things like logging and debugging can be trivially added and removed. Note that parameters can also be problematic if treated naively:

LogStatus("added: " + engine.DoImportantStuff());

and:

var count = engine.DoImportantStuff();
LogStatus("added: " + count);

can be very different if LogStatus is marked [Conditional] - with the result that your actual "important stuff" didn't get done.


Should it really behave differently based on whether the receiver of the conditional call is stored in a temporary variable or not?

Yes.

What's the rationale behind the C# compiler not evaluating a.GetB() in this situation?

The answers from Marc and Søren are basically correct. This answer is just to clearly document the timeline.

  • The feature was designed in 1999, and the intention of the feature was always to remove the entire statement.
  • The design notes from 2003 indicate that the design team realized then that the spec was unclear on this point. Up until this point the specification only called out that arguments would not be evaluated. I note that the spec makes the common mistake of calling the arguments "parameters", though of course one could suppose that they meant "actual parameters" rather than "formal parameters".
  • A work item was supposed to be created to fix the ECMA specification on this point; apparently that never happened.
  • The first time that the corrected text appears in any C# specification was the C# 4.0 specification, which I believe was 2010. (I do not recall if this was one of my corrections, or if someone else found it.)
  • If the 2017 ECMA specification does not contain this correction, then that's a mistake which should be fixed in the next release. Better 15 years late than never, I guess.

참고URL : https://stackoverflow.com/questions/49253356/why-does-the-c-sharp-compiler-remove-a-chain-of-method-calls-when-the-last-one-i

반응형