C # 3.0 개체 이니셜 라이저 생성자 괄호가 선택 사항 인 이유는 무엇입니까?


114

C # 3.0 개체 이니셜 라이저 구문을 사용하면 매개 변수가없는 생성자가있을 때 생성자에서 열기 / 닫기 쌍 괄호를 제외 할 수 있습니다. 예:

var x = new XTypeName { PropA = value, PropB = value };

반대 :

var x = new XTypeName() { PropA = value, PropB = value };

생성자 열기 / 닫기 괄호 쌍이 여기에서 선택 사항 인 이유가 궁금합니다 XTypeName.


9
제쳐두고 우리는 지난주 코드 리뷰에서 이것을 발견했습니다. var list = new List <Foo> {}; 무언가가 남용 될 수 있다면 ...
blu

@blu 이것이 제가이 질문을하고 싶었던 한 가지 이유입니다. 우리 코드에서 불일치를 발견했습니다. 일반적으로 불일치는 나를 괴롭히기 때문에 구문의 선택성 뒤에 좋은 근거가 있는지 알 것이라고 생각했습니다. :)
James Dunne

답변:


143

이 질문은 2010 년 9 월 20 일 제 블로그의 주제였습니다 . Josh와 Chad의 답변 ( "그들은 가치를 추가하지 않는데 왜 필요한가?"및 "중복성을 제거하기 위해")은 기본적으로 정확합니다. 좀 더 구체화하려면 :

객체 이니셜 라이저의 "큰 기능"의 일부로 인수 목록을 제거 할 수있는 기능은 "sugary"기능에 대한 기준을 충족했습니다. 우리가 고려한 몇 가지 사항 :

  • 설계 및 사양 비용이 낮았습니다.
  • 어쨌든 객체 생성을 처리하는 파서 코드를 광범위하게 변경하려고했습니다. 매개 변수 목록을 선택적으로 만드는 추가 개발 비용은 더 큰 기능의 비용에 비해 크지 않았습니다.
  • 더 큰 기능의 비용에 비해 테스트 부담이 상대적으로 적음
  • 문서화 부담이 상대적으로 적었습니다 ...
  • 유지 관리 부담이 적을 것으로 예상되었습니다. 이 기능이 출시 된 이후 몇 년 동안보고 된 버그는 기억 나지 않습니다.
  • 이 기능은이 영역의 향후 기능에 즉각적으로 명백한 위험을 초래하지 않습니다. (마지막으로하고 싶은 것은 지금 저렴하고 쉬운 기능을 만드는 것입니다. 이렇게하면 앞으로 더 매력적인 기능을 구현하기가 훨씬 더 어려워집니다.)
  • 이 기능은 언어의 어휘, 문법 또는 의미 분석에 새로운 모호성을 추가하지 않습니다. 입력하는 동안 IDE의 "IntelliSense"엔진이 수행하는 일종의 "부분 프로그램"분석에는 문제가 없습니다. 등등.
  • 이 기능은 더 큰 객체 초기화 기능에 대해 공통적 인 "스위트 스팟"에 도달합니다. 객체의 생성자가 없기 때문에이 정확히 초기화는 객체를 사용하는 일반적 경우 하지 원하는 속성을 설정할 수 있습니다. 이러한 오브젝트는 애초에 ctor에 매개 변수가없는 "속성 가방"이되는 것이 매우 일반적입니다.

그렇다면 객체 이니셜 라이저 가 없는 객체 생성 표현식의 기본 생성자 호출에서 빈 괄호도 선택 사항으로 만들지 않은 이유는 무엇 입니까?

위의 기준 목록을 다시 살펴보십시오. 그중 하나는 변경으로 인해 프로그램의 어휘, 문법 또는 의미 분석에 새로운 모호성이 도입되지 않는다는 것입니다. 귀하의 제안 된 변경은 하지 의미 론적 분석 모호성을 소개 :

class P
{
    class B
    {
        public class M { }
    }
    class C : B
    {
        new public void M(){}
    }
    static void Main()
    {
        new C().M(); // 1
        new C.M();   // 2
    }
}

1 행은 새 C를 만들고 기본 생성자를 호출 한 다음 새 개체에서 인스턴스 메서드 M을 호출합니다. 2 행은 BM의 새 인스턴스를 만들고 기본 생성자를 호출합니다. 1 행의 괄호가 선택 사항 인 경우 2 행은 모호합니다. 그런 다음 모호성을 해결하는 규칙을 만들어야합니다. 기존의 합법적 인 C # 프로그램을 손상된 프로그램으로 변경하는 주요 변경 사항이므로 오류로 만들 수 없습니다 .

따라서 규칙은 매우 복잡해야합니다. 본질적으로 괄호는 모호성을 도입하지 않는 경우에만 선택 사항입니다. 모호성을 유발하는 모든 가능한 경우를 분석 한 다음이를 감지하기 위해 컴파일러에서 코드를 작성해야합니다.

그런 관점에서 돌아가서 내가 언급 한 모든 비용을 살펴보십시오. 이제 얼마나 많이 커지나요? 복잡한 규칙에는 설계, 사양, 개발, 테스트 및 문서화 비용이 많이 듭니다. 복잡한 규칙은 향후 기능과의 예기치 않은 상호 작용에 문제를 일으킬 가능성이 훨씬 더 높습니다.

무엇을 위해? 언어에 새로운 표현력을 추가하지 않는 작은 고객 혜택이지만, 그 언어에 부딪 치는 의심하지 않는 일부 영혼에게 "gotcha"라고 소리를 지르기 위해 기다리는 미친 코너 케이스를 추가합니다. 이와 같은 기능은 즉시 제거 되고 "절대하지 않음"목록에 포함됩니다.

특정 모호성을 어떻게 결정 했습니까?

그것은 즉시 분명했습니다. 점으로 구분 된 이름이 예상되는시기를 결정하기위한 C #의 규칙에 대해 잘 알고 있습니다.

새로운 기능을 고려할 때 모호성을 유발하는지 여부를 어떻게 결정합니까? 손으로, 공식적인 증명으로, 기계 분석으로 무엇을?

세 개 모두. 대부분 우리는 위에서 한 것처럼 사양과 국수를 봅니다. 예를 들어 "frob"이라는 새 접두사 연산자를 C #에 추가하려고한다고 가정합니다.

x = frob 123 + 456;

(업데이트 : frob당연히 await; 여기서 분석은 본질적으로 디자인 팀이를 추가 할 때 겪은 분석입니다 await.)

여기에서 "frob"은 "new"또는 "++"와 같습니다. 일종의 표현 앞에옵니다. 원하는 우선 순위와 연관성 등을 파악한 다음 "프로그램에 이미 유형, 필드, 속성, 이벤트, 메서드, 상수 또는 frob이라는 로컬이있는 경우 어떻게됩니까?"와 같은 질문을 시작합니다. 그러면 다음과 같은 경우가 즉시 발생합니다.

frob x = 10;

"x = 10의 결과에 대해 frob 연산을 수행하거나 x라는 frob 유형의 변수를 생성하고 여기에 10을 할당합니까?"라는 의미입니까? (또는 frobbing이 변수를 생성하는 경우 10에 대한 할당이 될 수 있습니다 frob x. 결국 *x = 10;구문 분석을 수행하고이면 합법적 x입니다 int*.)

G(frob + x)

"x에 대한 단항 더하기 연산자의 결과를 frob"또는 "x에 표현식 frob 추가"를 의미합니까?

등등. 이러한 모호함을 해결하기 위해 휴리스틱을 도입 할 수 있습니다. "var x = 10;"이라고 말하면 그것은 모호합니다. "x의 유형을 추론"하거나 "x가 var 유형입니다"를 의미 할 수 있습니다. 그래서 우리는 휴리스틱을 가지고 있습니다. 먼저 var라는 유형을 찾아 보려고 시도하고, 존재하지 않는 경우에만 x 유형을 추론합니다.

또는 모호하지 않도록 구문을 변경할 수 있습니다. C # 2.0을 설계 할 때 다음과 같은 문제가있었습니다.

yield(x);

이것은 "반복기에서 x를 산출"또는 "인수 x로 yield 메서드를 호출"을 의미합니까? 그것을 변경함으로써

yield return(x);

이제 모호하지 않습니다.

객체 이니셜 라이저의 선택적 괄호의 경우 {로 시작하는 것을 도입하는 것이 허용되는 상황의 수가 매우 적기 때문에 도입 된 모호성이 있는지 여부를 추론하는 것이 간단합니다 . 기본적으로 다양한 문 컨텍스트, 문 람다, 배열 이니셜 라이저 및 그게 전부입니다. 모든 경우를 추론하고 모호함이 없음을 보여주는 것은 쉽습니다. IDE의 효율성을 유지하는 것은 다소 어렵지만 너무 많은 문제없이 수행 할 수 있습니다.

이러한 종류의 사양을 다루면 일반적으로 충분합니다. 특히 까다로운 기능이라면 더 무거운 도구를 꺼냅니다. 예를 들어 LINQ를 설계 할 때 컴파일러 전문가 중 한 명과 파서 이론에 대한 배경 지식이있는 IDE 전문가 중 한 명이 모호성을 찾는 문법을 분석 할 수있는 파서 생성기를 구축 한 다음 쿼리 이해를 위해 제안 된 C # 문법을 입력했습니다. ; 이렇게하면 쿼리가 모호한 경우가 많이 발견되었습니다.

또는 C # 3.0에서 람다에 대한 고급 유형 추론을 수행 할 때 제안서를 작성한 다음이를 연못을 통해 케임브리지의 Microsoft Research로 보냈습니다. 그곳에서 언어 팀은 유형 추론 제안이 다음과 같다는 공식적인 증거를 작성하기에 충분했습니다. 이론적으로 건전합니다.

오늘날 C #에 모호한 부분이 있습니까?

확실한.

G(F<A, B>(0))

C # 1에서는 그것이 의미하는 바가 분명합니다. 다음과 같습니다.

G( (F<A), (B>0) )

즉, bool 인 두 개의 인수로 G를 호출합니다. C # 2에서는 C # 1에서 의미하는 바를 의미 할 수 있지만 "형식 매개 변수 A와 B를 사용하는 일반 메서드 F에 0을 전달한 다음 F의 결과를 G에 전달"을 의미 할 수도 있습니다. 파서에 복잡한 휴리스틱을 추가하여 두 가지 경우 중 어떤 것을 의미하는지 결정했습니다.

마찬가지로 C # 1.0에서도 캐스트가 모호합니다.

G((T)-x)

"cast -x to T"또는 "subtract x from T"입니까? 다시, 우리는 좋은 추측을하는 휴리스틱을 가지고 있습니다.


3
오 죄송합니다, 잊었습니다 ... 박쥐 신호 접근 방식은 작동하는 것처럼 보이지만 (IMO) 직접적인 접촉 수단보다 선호됩니다. 즉, SO 게시물 형식으로 공공 교육을 위해 원하는 대중의 노출을 얻지 못합니다. 색인이 가능하고 검색 가능하며 쉽게 참조 할 수 있습니다. 대신 무대 SO 포스트 안무 / 답변 댄스를 위해 직접 연락할까요? :)
James Dunne

5
게시물을 준비하지 않는 것이 좋습니다. 그것은 질문에 대한 추가 통찰력을 가진 다른 사람들에게 공평하지 않을 것입니다. 더 나은 접근 방식은 질문을 게시 한 다음 참여를 요청하는 링크를 이메일로 보내는 것입니다.
chilltemp

1
@James : 귀하의 후속 질문을 해결하기 위해 제 답변을 업데이트했습니다.
Eric Lippert

8
@Eric,이 "안 함"목록에 대해 블로그를 작성할 수 있습니까? 나는 :) C # 언어의 일부가 결코 다른 예를보고 궁금해서
일리아 Ryzhenkov

2
@Eric : 정말 정말 정말 나를 기다려 주셔서 감사합니다 :) 감사합니다! 매우 유익합니다.
James Dunne

12

그것이 언어가 지정된 방법이기 때문입니다. 가치를 추가하지 않는데 왜 포함시킬까요?

또한 암시 유형 배열과 매우 유사합니다.

var a = new[] { 1, 10, 100, 1000 };            // int[]
var b = new[] { 1, 1.5, 2, 2.5 };            // double[]
var c = new[] { "hello", null, "world" };      // string[]
var d = new[] { 1, "one", 2, "two" };         // Error

참조 : http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx


1
의도가 무엇인지 명확해야한다는 의미에서 가치를 추가하지 않지만, 이제는 괄호가 필요한 (및 쉼표로 구분 된 인수 표현식) 하나는없는 두 개의 서로 다른 객체 구성 구문이 있다는 점에서 일관성이 깨집니다. .
James Dunne

1
@James Dunne, 실제로 암시 적으로 형식화 된 배열 구문과 매우 유사한 구문입니다. 내 편집을 참조하십시오. 유형도 생성자도없고 의도가 분명하므로 선언 할 필요가 없습니다.
CaffGeek

7

이것은 객체의 구성을 단순화하기 위해 수행되었습니다. 언어 디자이너는 C # 버전 3.0 사양 페이지에 명시 적으로 언급되어 있지만 이것이 유용하다고 생각하는 이유를 구체적으로 언급하지 않았습니다 .

객체 생성 표현식은 객체 또는 컬렉션 이니셜 라이저가 포함 된 경우 생성자 인수 목록과 괄호를 생략 할 수 있습니다. 생성자 인수 목록을 생략하고 괄호를 묶는 것은 빈 인수 목록을 지정하는 것과 같습니다.

개체 이니셜 라이저가 개체의 속성을 구성하고 설정하려는 의도를 보여주기 때문에 개발자 의도를 표시하기 위해 괄호가 필요하지 않다고 생각한다고 가정합니다.


4

첫 번째 예제에서 컴파일러는 기본 생성자를 호출하고 있다고 추론합니다 (C # 3.0 언어 사양에서는 괄호가 제공되지 않으면 기본 생성자가 호출된다고 명시되어 있습니다).

두 번째에서는 명시 적으로 기본 생성자를 호출합니다.

이 구문을 사용하여 속성을 설정하고 생성자에 값을 명시 적으로 전달할 수도 있습니다. 다음 클래스 정의가있는 경우 :

public class SomeTest
{
    public string Value { get; private set; }
    public string AnotherValue { get; set; }
    public string YetAnotherValue { get; set;}

    public SomeTest() { }

    public SomeTest(string value)
    {
        Value = value;
    }
}

세 가지 문이 모두 유효합니다.

var obj = new SomeTest { AnotherValue = "Hello", YetAnotherValue = "World" };
var obj = new SomeTest() { AnotherValue = "Hello", YetAnotherValue = "World"};
var obj = new SomeTest("Hello") { AnotherValue = "World", YetAnotherValue = "!"};

권리. 귀하의 예에서 첫 번째와 두 번째 경우에는 기능적으로 동일합니다. 맞습니까?
James Dunne

1
@James Dunne-맞습니다. 이것이 언어 사양에 지정된 부분입니다. 빈 괄호는 중복되지만 여전히 제공 할 수 있습니다.
Justin Niessner

1

나는 Eric Lippert가 아니므로 확실하게 말할 수는 없지만 초기화 구조를 추론하기 위해 컴파일러에서 빈 괄호가 필요하지 않기 때문이라고 가정합니다. 따라서 중복 정보가되며 필요하지 않습니다.


맞아요, 중복인데 왜 갑작스런 도입이 선택 사항인지 궁금합니다. 언어 구문 일관성이 깨지는 것 같습니다. 이니셜 라이저 블록을 나타내는 여는 중괄호가 없으면 잘못된 구문이어야합니다. 리퍼 트 씨를 언급하시는 게 재미 있네요. 저는 저와 다른 사람들이 한가한 호기심으로부터 혜택을받을 수 있도록 그의 대답을 공개적으로 낚시하고있었습니다. :)
James Dunne
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.