web-dev-qa-db-ja.com

C#3.0オブジェクト初期化子コンストラクターの括弧がオプションなのはなぜですか?

C#3.0オブジェクト初期化子構文では、パラメーターなしのコンストラクターが存在する場合、コンストラクター内のかっこの開き/閉じペアを除外できるように思われます。例:

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

とは対照的に:

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

ここでXTypeName?の後にコンストラクタの括弧のペアがオプションであるのはなぜですか?

110
James Dunne

この質問は2010年9月20日の私のブログの主題でした 。 JoshとChadの答え(「彼ら​​は価値を追加しないので、なぜそれらを必要とするのか?」および「冗長性を排除する」)は基本的に正しいです。それをもう少し具体化するには:

オブジェクト初期化子の「より大きな機能」の一部として引数リストを削除できる機能は、「シュガー」機能の基準を満たしました。私たちが考慮したいくつかのポイント:

  • 設計と仕様のコストが低かった
  • とにかく、オブジェクトの作成を処理するパーサーコードを大幅に変更します。パラメータリストをオプションにする追加の開発コストは、より大きな機能のコストと比較して大きくはありませんでした
  • より大きな機能のコストと比較して、テストの負担は比較的少なかった
  • ドキュメントの負担は比較的小さかった...
  • メンテナンスの負担は小さいと予想されました。この機能が出荷されてから数年間、この機能で報告されたバグを思い出せません。
  • この機能は、この領域の将来の機能にすぐに明らかなリスクをもたらすことはありません。 (私たちがやりたい最後のことは、将来、より魅力的な機能を実装することをはるかに難しくする、安価で簡単な機能を作ることです。)
  • この機能は、言語の字句解析、文法解析、または意味解析に新しい曖昧さを追加しません。入力中にIDEの「IntelliSense」エンジンによって実行される「部分プログラム」分析のような問題は発生しません。等々。
  • この機能は、より大きなオブジェクト初期化機能の共通の「スイートスポット」に当たります。通常、オブジェクト初期化子を使用している場合は、オブジェクトのコンストラクターがnotを使用して必要なプロパティを設定できるためです。このようなオブジェクトは、そもそもctorにパラメーターを持たない「プロパティバッグ」であることが非常に一般的です。

それから、notオブジェクト初期化子を持たないオブジェクト作成式のデフォルトのコンストラクター呼び出しで、空の括弧もオプションにしないのはなぜですか?

上記の基準のリストをもう一度見てください。それらの1つは、変更がプログラムの字句解析、文法解析、または意味解析に新しいあいまいさをもたらさないことです。提案された変更doesは意味解析のあいまいさをもたらします:

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行目は、B.Mの新しいインスタンスを作成し、そのデフォルトコンストラクターを呼び出します。 1行目の括弧がオプションである場合、2行目はあいまいになります。次に、あいまいさを解決するルールを考え出す必要があります。 errorにすることはできませんでした。これは、既存の有効なC#プログラムを破損したプログラムに変更する重大な変更になるためです。

したがって、ルールは非常に複雑にする必要があります。本質的に、括弧はあいまいさをもたらさない場合にのみオプションであるということです。あいまいさをもたらす可能性のあるすべてのケースを分析し、コンパイラでコードを記述してそれらを検出する必要があります。

その観点から、戻って、私が言及したすべての費用を見てください。それらのうちのどれだけが現在大きくなっていますか?複雑なルールには、設計、仕様、開発、テスト、およびドキュメントのコストがかかります。複雑なルールは、将来の機能との予期しない相互作用で問題を引き起こす可能性がはるかに高くなります。

何のために?言語に新たな表現力を追加することはありませんが、それに出くわす貧弱な疑いを持たない魂に「ゴチャ」と叫ぶのを待っているクレイジーなコーナーケースを追加する小さな顧客の利益。そのような機能は、すぐにカットされ、「これをしない」リストに追加されます。

特定のあいまいさをどのように判断しましたか?

それはすぐに明らかでした。ドット表記の名前がいつ期待されるかを決定するためのC#のルールにかなり精通しています。

新機能を検討するとき、それがあいまいさを引き起こすかどうかをどのように判断しますか?手で、形式的証明で、機械分析で、何を?

3つすべて。上記のように、ほとんどの場合、仕様とその麺を見るだけです。たとえば、C#に「frob」という新しいプレフィックス演算子を追加するとします。

x = frob 123 + 456;

(更新:frobはもちろんawaitです。ここでの分析は、基本的にawaitを追加するときに設計チームが行った分析です。)

ここでの「frob」は「new」または「++」に似ています-何らかの表現の前にあります。目的の優先順位と結合性などを決定し、「プログラムに既にタイプ、フィールド、プロパティ、イベント、メソッド、定数、またはfrobと呼ばれるローカルがある場合はどうするか」などの質問を開始します。それはすぐに次のような場合につながります:

frob x = 10;

それは「x = 10の結果に対してfrob操作を行うか、xというfrob型の変数を作成して10を割り当てるか」という意味ですか? (または、フロビングが変数を生成する場合、frob xに10を割り当てることができます。結局、*x = 10;は解析され、xint*であれば正当です。)

G(frob + x)

「xの単項プラス演算子の結果をフロブする」または「xに式フロブを追加する」という意味ですか?

等々。これらのあいまいさを解決するために、ヒューリスティックを導入できます。 「var x = 10;」と言うときそれはあいまいです。 「xの型を推測する」ことを意味する場合もあれば、「xがvar型である」ことを意味する場合もあります。そのため、ヒューリスティックがあります。最初にvarという名前の型を検索しようとし、存在しない場合にのみxの型を推測します。

または、あいまいにならないように構文を変更する場合があります。 C#2.0を設計したとき、次の問題がありました。

yield(x);

これは、「イテレータでxを返す」または「引数xでyieldメソッドを呼び出す」という意味ですか?に変更することにより

yield return(x);

現在は明確です。

オブジェクト初期化子のオプションの括弧の場合、{ 。基本的には、さまざまなステートメントコンテキスト、ステートメントラムダ、配列初期化子などです。すべてのケースを通して推論することは簡単で、曖昧さがないことを示します。 IDEが効率的であることを確認することは多少困難ですが、あまり手間をかけずに行うことができます。

通常、この種の仕様をいじるだけで十分です。特に扱いにくい機能である場合は、より重いツールを引き出します。たとえば、LINQを設計するとき、コンパイラーの人の1人とIDEクエリの理解のために提案されたC#文法をフィードしました;そうすると、クエリがあいまいな多くのケースが見つかりました。

または、C#3.0でラムダの高度な型推論を行ったとき、提案を作成し、それをケンブリッジのMicrosoft Researchに池で送りました。理論的には健全。

今日のC#にはあいまいさがありますか?

承知しました。

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

C#1では、それが何を意味するかは明確です。以下と同じです:

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

つまり、ブールである2つの引数を使用してGを呼び出します。 C#2では、C#1で意味したことを意味しますが、「型パラメーターAおよびBを受け取るジェネリックメソッドFに0を渡し、Fの結果をGに渡す」ことも意味します。パーサーに複雑なヒューリスティックを追加して、2つのケースのどちらを意味するかを判断します。

同様に、C#1.0でもキャストはあいまいです。

G((T)-x)

それは「-xをTにキャスト」または「Tからxを減算」ですか?繰り返しになりますが、推測に基づいたヒューリスティックがあります。

140
Eric Lippert

それが言語の指定方法だからです。付加価値がないのに、なぜそれらを含めるのですか?

また、暗黙の型付き配列に非常に似ています

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

10
CaffGeek

これは、オブジェクトの構築を簡素化するために行われました。言語設計者は、(私の知る限りでは) C#バージョン3.0仕様ページ で明示的に言及されているものの、なぜこれが有用であると感じたのかを具体的に述べていません。

オブジェクト作成式では、オブジェクトまたはコレクションの初期化子が含まれている場合、コンストラクター引数リストと括弧を省略できます。コンストラクター引数リストを省略して括弧を囲むことは、空の引数リストを指定することと同等です。

オブジェクト初期化子が代わりにオブジェクトのプロパティを構築および設定する意図を示すため、開発者の意図を示すために、この例では括弧は必要ないと感じたと思います。

7
Reed Copsey

最初の例では、コンパイラはデフォルトコンストラクターを呼び出していると推測します(C#3.0言語仕様では、括弧が指定されていない場合、デフォルトコンストラクターが呼び出されると規定されています)。

2番目では、デフォルトのコンストラクターを明示的に呼び出します。

また、その構文を使用して、コンストラクターに値を明示的に渡しながらプロパティを設定することもできます。次のクラス定義がある場合:

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;
    }
}

3つのステートメントはすべて有効です。

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

私はエリック・リッパーではないので、確かに言うことはできませんが、初期化コンストラクトを推論するためにコンパイラーが空の括弧を必要としないためだと思います。したがって、それは冗長な情報となり、必要ありません。

1
Josh