web-dev-qa-db-ja.com

C#8のnull不可の参照とTryパターン

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をインスタンス化するとどうなりますか?

24
Sebastian Redl

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許容値の型が大きく異なります。これにはさまざまな回避策があります。

  1. NRT機能を有効にしないでください。
  2. コードがnullなしでサインアップしている場合でも、defaultパラメータにoutを(!とともに)使用し始めます。
  3. 実際のMaybe<T>タイプを戻り値として使用します。これは決してnullではなく、そのboolout THasValueValueプロパティなど
  4. タプルを使用する:
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のようにタプルとしてパターンマッチングできるように、分解をサポートするようにしています。

11
David Arno

私のようにこれに少し遅れて到着した場合、.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"

詳細:

18
Nick Darvey

ここで対立はないと思います。

あなたの反対

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);
3
Ewan