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

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

C# 3.0 オブジェクト初期化構文では、パラメータなしのコンストラクターが存在する場合、コンストラクター内の開き括弧と閉じ括弧のペアを除外できるようです。例:

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

とは対照的に:

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

コンストラクターの開始/終了括弧のペアが、ここ以降オプションになっているのはなぜでしょうかXTypeName?

ベストアンサー1

この質問は2010年9月20日の私のブログのテーマでしたJosh と Chad の回答 (「価値を追加しないのに、なぜ必要なのか?」および​​「冗長性を排除するため」) は基本的に正しいです。これをもう少し詳しく説明します。

オブジェクト初期化子の「より大きな機能」の一部として引数リストを省略できるようにする機能は、「甘い」機能の基準を満たしていました。検討した点は次のとおりです。

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

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

上記の基準のリストをもう一度見てみましょう。その1つは、変更によってプログラムの語彙、文法、意味の分析に新たな曖昧さが生じないことです。提案された変更は、する意味解析の曖昧さを導入する:

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# プログラムを壊れたプログラムに変更する重大な変更となるからです。

したがって、ルールは非常に複雑になります。つまり、括弧は、曖昧さを生じない場合にのみオプションになります。曖昧さを生じさせる可能性のあるすべてのケースを分析し、それらを検出するためのコードをコンパイラーに記述する必要があります。

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

一体何のため? 言語に新しい表現力を追加しない小さな顧客利益ですが、それに遭遇した哀れな無防備な魂に「やったー」と叫ぶのを待っているクレイジーなコーナーケースを追加します。そのような機能はカットされますすぐにそして「絶対にやらないこと」リストに入れます。

その特定の曖昧さをどのように判断したのですか?

それはすぐに分かりました。ドット付きの名前がいつ必要になるかを判断するための C# のルールには、私はかなり精通しています。

新しい機能を検討する際、それが曖昧さを引き起こすかどうかをどのように判断しますか? 手作業、正式な証明、機械分析、どれですか?

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

x = frob 123 + 456;

(更新:frobは当然 ですawait。ここでの分析は、基本的に を追加する際に設計チームが行った分析ですawait。)

ここでの「frob」は「new」や「++」のようなもので、何らかの式の前に来ます。望ましい優先順位や結合性などを考え、次に「プログラムにすでに frob という型、フィールド、プロパティ、イベント、メソッド、定数、またはローカルがある場合はどうなるか」といった質問を始めます。これはすぐに次のようなケースにつながります。

frob x = 10;

これは、「x = 10 の結果に対して frob 演算を実行するか、x と呼ばれる frob 型の変数を作成してそれに 10 を割り当てる」という意味ですか? (または、frob によって変数が生成される場合は、 に 10 を割り当てることになりますfrob x。結局のところ、は を解析し、の*x = 10;場合は が正当です。)xint*

G(frob + x)

これは、「x の単項プラス演算子の結果を frob する」という意味ですか、それとも「x に式 frob を追加する」という意味ですか?

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

あるいは、曖昧にならないように構文を変更するかもしれません。C# 2.0 を設計したとき、次のような問題がありました。

yield(x);

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

yield return(x);

それは今や明白です。

オブジェクト初期化子のオプションの括弧の場合、曖昧さが導入されているかどうかは簡単に推測できます。{で始まるものを導入することが許される状況の数は非常に少ない基本的には、さまざまなステートメント コンテキスト、ステートメント ラムダ、配列初期化子などです。すべてのケースを検討して、あいまいさがないことを示すのは簡単です。IDE の効率性を維持することは多少困難ですが、それほど苦労せずに実行できます。

通常は、このような仕様の調整で十分です。特に扱いにくい機能の場合は、より強力なツールを使います。たとえば、LINQ を設計する際、コンパイラ担当者の 1 人と IDE 担当者の 1 人は、どちらもパーサー理論のバックグラウンドを持っており、文法を分析して曖昧さを探すパーサー ジェネレーターを独自に構築し、クエリの理解のために提案された 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 を減算する」ですか? 繰り返しますが、適切な推測を行うヒューリスティックがあります。

おすすめ記事