Null 許容の有効なコンテキストの場合、コンパイラによってコードの静的分析が実行され、すべての参照型変数の "null 状態" が判断されます。
- "null 以外": 静的分析によって、変数の値が null 以外であることが判断されます。
- maybe-null: 静的分析では、変数が null 以外の値に割り当てられていることを判断できません。
これらの状態により、null 値を逆参照して System.NullReferenceExceptionをスローする可能性がある場合に、コンパイラは警告を表示できます。 これらの属性は、引数、戻り値、およびオブジェクト メンバーの null 状態 に関するセマンティック情報をコンパイラに提供します。 属性により、引数と戻り値の状態が明確になります。 コンパイラは、API にこのセマンティック情報が適切に注釈付けされている場合に、より正確な警告を提供します。
この記事では、null 許容参照型の各属性とその使用方法について簡単に説明します。
最初に例を見てみましょう。 ライブラリに、リソース文字列を取得する次の API があるとします。 このメソッドはもともと null 許容の未指定コンテキストでコンパイルされました。
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
前述の例では、.NET のよくある key および message) があります。 この API には、これらのパラメーターの "null 状態" に関連する次の規則があります。
- 呼び出し元では、
`key` の引数として`null` を渡すことはできません。 - 呼び出し元では、
`message` の引数として、値が`null` の変数を渡すことができます。 `TryGetMessage` メソッドから`true` が返された場合、`message` の値は null ではありません。 戻り値がfalseの場合、messageの値は null です。
key の規則は簡潔に表すことができます。key は null 非許容参照型である必要があります。 null である変数が許容されますが、成功時には out の引数が null ではないことが保証されます。 これらのシナリオでは、予測を記述するために、より豊富なボキャブラリが必要です。
NotNullWhen属性は、message パラメーターに使用される引数の null 状態を記述します。
注意
これらの属性を追加すると、API の規則に関する詳細情報がコンパイラに与えられます。 呼び出し元のコードが null 許容の有効なコンテキストでコンパイルされると、コンパイラは、呼び出し元がこれらの規則に違反したときに警告します。 このような属性では、実装に対するその他のチェックが有効になりません。
| 属性 | カテゴリ | 説明 |
|---|---|---|
| null 非許容パラメーター、フィールド、またはプロパティは null である可能性があります。 | ||
| Null 許容のパラメーター、フィールド、またはプロパティは、null にすることができません。 | ||
| null 非許容パラメーター、フィールド、プロパティ、または戻り値は null である可能性があります。 | ||
| null 許容パラメーター、フィールド、プロパティ、または戻り値が null になることはありません。 | ||
メソッドが指定した bool 値を返す場合、null 非許容引数は null になる可能性があります。 |
||
メソッドが指定した bool 値を返す場合、null 許容引数は null ではありません。 |
||
| 指定されたパラメーターの引数が null でない場合、戻り値、プロパティ、または引数は null ではありません。 | ||
| メソッドおよびプロパティ ヘルパー メソッド | リストされているメンバーは、メソッドが返されるときに null ではありません。 | |
| メソッドおよびプロパティ ヘルパー メソッド | 指定した bool 値をメソッドが返す場合、リストされているメンバーは null ではありません。 |
|
| メソッドまたはプロパティが返されることはありません。 つまり、常に例外がスローされます。 | ||
関連付けられた bool パラメーターに指定された値がある場合、このメソッドまたはプロパティから制御が返されることはありません。 |
前述の説明は、各属性動作に関するクイック リファレンスです。 以下のセクションでは、これらの属性の動作と意味についてさらに詳しく説明します。
事前条件: `AllowNull` と `DisallowNull`
適切な既定値が設定されているため、
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
null 許容の認識されないコンテキストで上記のコードをコンパイルする場合、何も問題はありません。 null 許容参照型を有効にすると、
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
この記事で説明するその他の属性を使用するには、System.Diagnostics.CodeAnalysisのusing ディレクティブを追加する必要がある場合があります。 属性は、
前の例では、引数に
- その変数の一般的なコントラクトは、
`null` にできないため、null 非許容参照型が必要であるというものです。 - 呼び出し元から
nullが引数として渡されるシナリオもありますが、よくある使用方法ではありません。
ほとんどの場合、この属性は、プロパティ、 in、 out、および ref 引数に必要です。
public string ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;
上記のコードは、
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
null 許容コンテキストでは、ReviewCommentget アクセサーから null の既定値が返される可能性があります。 コンパイラからは、アクセスの前にチェックする必要があることが警告されます。 さらに、
- 主なシナリオでは (多くの場合、最初にインスタンス化されるときに)、変数が
`null` である可能性があります。 - 変数は、明示的に
`null` に設定することはできません。
このような状況は、もともと "
AllowNull属性とDisallowNull属性を使用すると、変数の前提条件が、それらの変数の null 許容注釈と一致しない可能性があることを指定できます。 これらの注釈は、API の特性の詳細を提供します。 この追加情報は、呼び出し元で API を正しく使用するのに役立ちます。 次の属性を使用して、事前条件を指定することを忘れないでください。
- AllowNull: null 非許容引数は null である可能性があります。
[ DisallowNull](xref:System.Diagnostics.CodeAnalysis.DisallowNullAttribute) : null 許容引数は null にすることはできません。
事後条件: `MaybeNull` および `NotNull`
次のシグネチャを持つメソッドがあるとします。
public Customer FindCustomer(string lastName, string firstName)
求められた名前が見つからなかったときに null を返すために、このようなメソッドを記述した可能性があります。
public Customer? FindCustomer(string lastName, string firstName)
ジェネリックの null 許容で説明されている理由から、この手法では API に一致する静的分析が生成されない可能性があります。 同様のパターンに従うジェネリック メソッドがある場合があります。
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
検索された項目が見つからない場合、このメソッドからは null の注釈を追加することで、項目から見つからない場合にメソッドから MaybeNull を返すことを明確にすることができます。
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
上記のコードは、戻り値が実際に null になる 可能性 があることを呼び出し元に通知します。 また、型が null 非許容であっても、メソッドが null 式を返すことができることもコンパイラに通知します。 型パラメーター T のインスタンスを返すジェネリック メソッドがある場合、null 属性を使用して NotNull を返さないことを表現できます。
型が null 許容参照型である場合でも、戻り値または引数が null でないことを指定することもできます。 次のメソッドは、その最初の引数が
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
次のように、このルーチンを呼び出すことができます。
public static void LogMessage(string? message)
{
ThrowWhenNull(message, $"{nameof(message)} must not be null");
Console.WriteLine(message.Length);
}
null 参照型を有効にした後、確実に前のコードが警告なしでコンパイルされるようにする必要があります。 メソッドから制御が戻ったときに、value パラメーターが null でないことが保証されます。 しかし、null 参照で
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
// other logic elided
上記のコードは、既存のコントラクトを明確に表しています。呼び出し元では
無条件の事後条件は、次の属性を使用して指定します。
条件付きの事後条件: `NotNullWhen` 、`MaybeNullWhen` 、`NotNullIfNotNull`
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
これにより、戻り値が
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.
String.IsNullOrEmpty(String)メソッドは、前の例に示したとおりです。 オブジェクトの状態で null 値をチェックする同様のメソッドがコードベースにある場合があります。 コンパイラはカスタムの null チェック メソッドを認識しないため、注釈を自分で追加する必要があります。 属性を追加すると、コンパイラの静的分析は、テストされた変数が null チェックされるタイミングを認識します。
これらの属性のもう 1 つの用途は、ref および out 引数の事後条件は、戻り値を通じて伝達されます。 前述のメソッド (Null 許容が無効なコンテキスト) について考えてみましょう。
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
上記のメソッドは、一般的な .NET のイディオムに従います。戻り値は、 message が求められた値に設定されたか、メッセージが見つからない場合は既定値に設定されたかを示します。 メソッドから
Null 許容の有効なのコンテキストでは、NotNullWhen 属性を使用してその表現方法を伝達することができます。 Null 許容参照型のパラメーターに注釈を付けるときは、message を string? にして属性を追加します。
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
前述の例では、null である可能性があり、メソッドから true が返された場合は null ではないと認識されます。
必要になる可能性がある最終的な属性が 1 つあります。 戻り値の null 状態は、1 つまたは複数の引数の null 状態に依存する場合があります。 これらのメソッドは、特定の引数が nullされていない場合は常に null 以外の値を返します。 これらのメソッドに正しく注釈を付けるには、
string GetTopLevelDomainFromFullUrl(string url)
string? GetTopLevelDomainFromFullUrl(string? url)
これも機能しますが、多くの場合、呼び出し元は追加の null チェックを強制的に実装します。 コントラクトは、引数
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
前の例では、パラメーター nameof に演算子 url を使用しています。 戻り値と引数の両方に、url 引数が nullされていない場合に戻り値が null ではないことをさらに明確にしています。
条件付きの事後条件は、これらの属性を使用して指定します。
-
MaybeNullWhen: メソッドが指定した
bool値を返す場合、null 非許容引数を null にすることができます。 -
NotNullWhen: メソッドが指定した
bool値を返す場合、null 許容引数は null ではありません。 [ NotNullIfNotNull](xref:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute) :指定されたパラメーターの引数が null でない場合、戻り値は null ではありません。
ヘルパー メソッド: MemberNotNull と MemberNotNullWhen
これらの属性は、コンストラクターからヘルパー メソッドに共通コードをリファクタリングするときに意図を指定します。 C# コンパイラは、コンストラクターとフィールド初期化子を分析して、各コンストラクターが戻る前にすべての null 非許容参照フィールドが初期化されていることを確認します。 ただし、C# コンパイラによって、すべてのヘルパー メソッドを介したフィールドの割り当てが追跡されるわけではありません。 コンストラクターで直接ではなく、ヘルパー メソッドでフィールドが初期化されると、コンパイラから警告
public class Container
{
private string _uniqueIdentifier; // must be initialized.
private string? _optionalMessage;
public Container()
{
Helper();
}
public Container(string message)
{
Helper();
_optionalMessage = message;
}
[MemberNotNull(nameof(_uniqueIdentifier))]
private void Helper()
{
_uniqueIdentifier = DateTime.Now.Ticks.ToString();
}
}
呼び出したメソッドからスローされたときに Null 許容の分析を停止する
一部のメソッド (通常は例外ヘルパー、またはその他のユーティリティ メソッド) は、常に例外をスローして終了します。 または、ヘルパーはブール型の引数の値に基づいて例外をスローします。
最初のケースでは、メソッド宣言に DoesNotReturn" 分析によって確認されません。 たとえば、次のメソッドがあったとします。
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}
FailFast の呼び出しの後に、コンパイラからは警告が発行されません。
2 つ目のケースでは、メソッドのブール型パラメーターに
private void FailFastIf([DoesNotReturnIf(true)] bool isNull)
{
if (isNull)
{
throw new InvalidOperationException();
}
}
public void SetFieldState(object? containedField)
{
FailFastIf(containedField == null);
// No warning: containedField can't be null here:
_field = containedField;
}
引数の値が DoesNotReturnIf コンストラクターの値と一致した場合、そのメソッド後は、コンパイラによって "null 状態" 分析が実行されません。
まとめ
null 許容参照型を追加すると、
null 許容コンテキストのライブラリを更新する際には、API のユーザーに正しい使用方法を示すために、これらの属性を追加します。 これらの属性は、引数と戻り値の null 状態を完全に記述するのに役立ちます。
- AllowNull: null 非許容フィールド、パラメーター、またはプロパティは null である可能性があります。
- DisallowNull: Null 許容のフィールド、パラメーター、またはプロパティを null にすることはできません。
- MaybeNull: null 非許容フィールド、パラメーター、プロパティ、または戻り値は null である可能性があります。
- NotNull: null 許容フィールド、パラメーター、プロパティ、または戻り値が null になることはありません。
-
MaybeNullWhen: メソッドが指定した
bool値を返す場合、null 非許容引数は null になる可能性があります。 -
NotNullWhen: メソッドが指定した
bool値を返す場合、null 許容引数は null ではありません。 - NotNullIfNotNull: 指定されたパラメーターの引数が null でない場合、パラメーター、プロパティ、または戻り値は null ではありません。
- DoesNotReturn: メソッドまたはプロパティから返されることはありません。 つまり、常に例外がスローされます。
-
DoesNotReturnIf: 関連付けられた
boolパラメーターに指定された値がある場合、このメソッドまたはプロパティから制御が返されることはありません。
.NET