C#クラスには、Dictionary.TryGetValue
およびint.TryParse
で例示されるパターンがあります。操作の成功を示すブール値と実際の結果を含む出力パラメーターを返すメソッドです。操作が失敗した場合、出力パラメーターはnullに設定されます。
C#8のnull不可の参照を使用していて、自分のクラス用のTryParseメソッドを書きたいとしましょう。 正しい署名はこれです:
public static bool TryParse(string s, out MyClass? result);
Falseの場合、結果はnullになるため、out変数はnull可能としてマークする必要があります。
ただし、Tryパターンは通常、次のように使用されます。
if (MyClass.TryParse(s, out var result))
{
// use result here
}
操作が成功した場合にのみブランチに入るので、そのブランチでは結果がnullになることはありません。しかし、これをnullableとしてマークしたので、それを確認するか、!
を使用してオーバーライドする必要があります。
if (MyClass.TryParse(s, out var result))
{
Console.WriteLine("Look: {0}", result.SomeProperty); // compiler warning, could be null
Console.WriteLine("Look: {0}", result!.SomeProperty); // need override
}
これは醜く、少し人間工学的ではありません。
典型的な使用パターンのため、別の選択肢があります。結果のタイプについて嘘をつきます。
public static bool TryParse(string s, out MyClass result) // not nullable
{
// Happy path sets result to non-null and returns true.
// Error path does this:
result = null!; // override compiler complaint
return false;
}
今、典型的な使用法はより良くなります:
if (MyClass.TryParse(s, out var result))
{
Console.WriteLine("Look: {0}", result.SomeProperty); // no warning
}
しかし、非定型の使用法では、警告が表示されません。
else
{
Console.WriteLine("Fail: {0}", result.SomeProperty);
// Yes, result is in scope here. No, it will never be non-null.
// Yes, it will throw. No, the compiler won't warn about it.
}
ここに行く方法がわかりません。 C#言語チームからの公式の推奨事項はありますか?これを行う方法を私に示すことができるnull不可の参照に既に変換されたCoreFXコードはありますか? (私はTryParse
メソッドを探しに行きました。IPAddress
はそれを持つクラスですが、corefxのマスターブランチで変換されていません。)
そして、Dictionary.TryGetValue
のような一般的なコードはこれをどのように扱いますか? (おそらく、私が見つけたものからの特別なMaybeNull
属性を使用します。)null不可の値タイプでDictionary
をインスタンス化するとどうなりますか?
Bool/out-varパターンは、説明したように、null許容の参照型ではうまく機能しません。そのため、コンパイラと戦うのではなく、機能を使用して物事を簡素化します。 C#8の改善されたパターンマッチング機能を投入すると、null許容可能な参照を「貧乏人の多分型」として扱うことができます。
public static MyClass? TryParse(string s) => …
…
if (TryParse(someString) is {} myClass)
{
// myClass wasn't null, we are good to use it
}
こうすることで、out
パラメーターをいじるのを避け、null
とnull不可の参照を混在させることについてコンパイラーと戦う必要がなくなります。
そして、
Dictionary.TryGetValue
のような一般的なコードはこれをどのように扱いますか?
この時点で、その「貧乏人のたぶんタイプ」は落ちる。直面する課題は、null許容参照型(NRT)を使用する場合、コンパイラがFoo<T>
をnull許容不可として扱うことです。ただし、それをFoo<T?>
に変更してみてください。T
がクラスまたは構造体に制約されると、CLRの観点からはnull許容値の型が大きく異なります。これにはさまざまな回避策があります。
default
パラメータにout
を(!
とともに)使用し始めます。Maybe<T>
タイプを戻り値として使用します。これは決してnull
ではなく、そのbool
とout T
をHasValue
とValue
プロパティなどpublic static (bool success, T result) TryParse<T>(string s) => …
…
if (TryParse<MyClass>(someString) is (true, var result))
{
// result is valid here, as success is true
}
私は個人的にはMaybe<T>
の使用を支持していますが、上記の4のようにタプルとしてパターンマッチングできるように、分解をサポートするようにしています。
私のようにこれに少し遅れて到着した場合、.NETチームは、MaybeNullWhen(returnValue: true)
のようなSystem.Diagnostics.CodeAnalysis
スペースにある一連のパラメーター属性を通じて、パターンを試してください。
例えば:
dictionary.TryGetValueのような一般的なコードはこれをどのように処理しますか?
bool TryGetValue(TKey key, [MaybeNullWhen(returnValue: false)] out TValue value);
つまり、true
を確認しないと怒鳴られる
// This is okay:
if(myDictionary.TryGetValue("cheese", out var result))
{
var more = result * 42;
}
// But this is not:
_ = myDictionary.TryGetValue("cheese", out var result);
var more = result * 42;
// "CS8602: Dereference of a potentially null reference"
詳細:
ここで対立はないと思います。
あなたの反対
public static bool TryParse(string s, out MyClass? result);
です
操作が成功した場合にのみブランチに入るので、そのブランチでは結果がnullになることはありません。
ただし、実際には、古いスタイルのTryParse関数では、出力パラメーターへのnullの割り当てを妨げるものはありません。
例えば。
MyJsonObject.TryParse("null", out obj) //sets obj to a null MyJsonObject and returns true
プログラマがチェックせずにoutパラメータを使用するときにプログラマに与えられる警告は正しいです。あなたがチェックする必要があります!
コードのメインブランチがnullにできない型を返す場合、nullにできる型を強制的に返すケースがたくさんあります。警告は、これらを明示的にするためのものです。すなわち。
MyClass? x = (new List<MyClass>()).FirstOrDefault(i=>i==1);
Nullにできない方法でコード化すると、nullがあったはずの場所で例外がスローされます。解析、取得、または最初のいずれか
MyClass x = (new List<MyClass>()).First(i=>i==1);