先回のレポート"C#の今後 — パラメータnullバリデーションの簡略化"の時、この機能には属性やコンパイルフラグ、あるいはILウィービングによるフルスケールAPIなど、競合する提案の存在という課題があった。結果的にそれらはすべて、非nullパラメータを検証するために必要なコード量をわずか1文字に削減するという、最小限に設定された機能提案によって破棄されることになった。
否定演算子"!"をパラメータリスト内の任意の識別子の直後に置くことで、C#コンパイラが、そのパラメータの標準的なnullチェックを行うコードを出力するようになる。例えば、
void M(string name!) { ... }
上記のコードは、次のように変換される。
void M(string name!) { if (name is null) { throw new ArgumentNullException(nameof(name)); } ... }
ルールは極めて単純だ。
- 他のコードより前、関数の先頭に、関数シグネチャ内のパラメータと同じ順番で、nullチェックが挿入される。
- 実行されるのは参照型等価のnullチェックで、
==
演算子のオーバーロードは無視される。 - コンストラクタで使用された場合は、ベースクラスのコンストラクタがコールされた後に行われる。
- イテレータの場合には、"nullチェックは下位のメソッドではなく、常に最初のコールにおいて行わなければならない"。
- nullを設定できないパラメータ(structや非マネージド)にnullチェックが行われた場合は、コンパイラエラーが発生する。
- 対応する実装のないパラメータ(抽象メソッドやデリゲート、インターフェースメソッド、パーシャルメソッドなど)にnullチェックが行われた場合も、コンパイルエラーになる。
- outパラメータに対するnullチェックはコンパイラエラーとなる。
- 明示的にnull可能なパラメータにnullチェックが行われた場合(
string? x!
)、コンパイラが警告を発する。予想されるように、サブクラスではベースクラスより厳密にすることが可能である。 - nullデフォルト値を持つ省略可能なパラメータにnullチェックが行われた場合は、コンパイラが警告を発する。
コンストラクタのルールに関しては、ベースクラスのコンストラクタをコールする前にパラメータチェックが可能なように考慮されている。これは.NETでは常に許容されているので、間違いなく望ましい方法だ。しかしながら、C#の構文ではこの機能を公開していないので、この機能を使用しなければ作成できないコードの生成は望んでいなかった。
テスト計画では、ジェネリクスについて次のようなシナリオが示されている。
void M<T>(T value!) { } is OK
void M<T>(T value!) where T : struct { } is an error
void M<T>(T value!) where T : unmanaged { } is an error
void M<T>(T value!) where T : notnull { } is OK
void M<T>(T value!) where T : class { } is OK
void M<T>(T value!) where T : SomeStruct { } is an error
void M<T>(T value!) where T : SomeClass { } is OK
このデザインの問題点のひとつは、valueパラメータが明示的でないため、プロパティの検証がサポートされないことだ。Orthoxerox(ユーザ名)がこのシナリオに対して、setキーワードに否定演算子を適用する回避策を提案している。
public Foo Foo {get; set!;}
public Bar Bar {
get { return bar; }
set!{ bar = value; DoSomethingElse(); }
}