この質問は2010年9月20日の私のブログの主題でした。JoshとChadの回答(「価値がないので、なぜ必要なのか?」と「冗長性を排除する」)は基本的に正しいです。もう少し具体化するには:
オブジェクト初期化子の「より大きな機能」の一部として引数リストを省略できるという機能は、「甘い」機能の基準を満たしました。私たちが考慮したいくつかのポイント:
- 設計と仕様のコストが低かった
- とにかく、オブジェクトの作成を処理するパーサーコードを大幅に変更します。パラメータリストをオプションにする追加の開発コストは、より大きな機能のコストに比べてそれほど大きくありませんでした
- より大きな機能のコストと比較して、テストの負担は比較的少なかった
- 文書化の負担は比較して比較的少なかった...
- メンテナンスの負担は少ないと予想された。この機能が出荷されてから数年間、この機能で報告されたバグを思い出しません。
- この機能は、この領域の将来の機能にすぐに明らかなリスクをもたらすことはありません。(私たちがやりたい最後のことは、安価で簡単な機能を今すぐ作成することです。これにより、将来、より魅力的な機能を実装することが非常に難しくなります。)
- この機能により、言語の語彙、文法、または意味の分析に新しい曖昧さが追加されることはありません。入力中にIDEの「IntelliSense」エンジンによって実行される一種の「部分プログラム」分析には問題はありません。等々。
- この機能は、より大きなオブジェクト初期化機能の一般的な「スイートスポット」に到達します。通常、オブジェクト初期化子を使用している場合は、オブジェクトのコンストラクターで必要なプロパティを設定できないためです。そのようなオブジェクトが、そもそもctorにパラメータを持たない「プロパティバッグ」であるのは非常に一般的です。
オブジェクト初期化子を持たないオブジェクト作成式のデフォルトのコンストラクター呼び出しで空の括弧もオプションにしないのはなぜですか?
上記の基準のリストをもう一度見てください。それらの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 x = 10;
これは、「x = 10の結果に対してフロブ演算を行うか、またはxと呼ばれるフロブ型の変数を作成し、それに10を割り当てる」という意味ですか?(または、frobbingが変数を生成する場合、それは10のへの割り当てである可能性がありfrob x
ます。結局のところ、が*x = 10;
解析され、が正しい場合x
は有効ですint*
。)
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の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)
それは「cast -x to T」ですか、「Tからxを引く」ですか?繰り返しになりますが、私たちは良い推測をするヒューリスティックを持っています。