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


22

Dictionary.TryGetValueandで例示されるC#クラスにはパターンがありint.TryParseます。操作の成功を示すブール値と実際の結果を含む出力パラメーターを返すメソッド。操作が失敗した場合、outパラメーターは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になることはありません。しかし、null可能としてマークしたので、それを確認するか!、オーバーライドに使用する必要があります。

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。1つのクラスがありますが、corefxのmasterブランチでは変換されていません。)

そして、汎用コードDictionary.TryGetValueはこれをどのように処理しますか?(おそらく、MaybeNull私が見つけたものからの特別な属性を使用して。)Dictionarynullを許可しない値型でをインスタンス化するとどうなりますか?


試しませんでした(これが答えとしてこれを書いていない理由です)が、switchステートメントの新しいパターンマッチング機能では、1つのオプションは単純にnull可能な参照を返すことです(Tryパターンなし、 return MyClass?)、そしてcase MyClass myObj:(and optionall)で切り替えますcase null:
フィリップミロヴァーノヴィッチ

+1私はこの質問が本当に好きです、そしてこれで作業しなければならなかったとき、私は常にオーバーライドの代わりに余分なnullチェックを常に使用しました-これは常に少し不必要で洗練されていないと感じましたが、パフォーマンスクリティカルなコードには決してありませんでしただから手放します。それを処理するよりクリーンな方法があるかどうかを見るといいでしょう!
BrianH

回答:


10

説明したように、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パラメーターをいじるのを避けることができ、nullnull不可の参照と混合することでコンパイラーと戦う必要はありません。

そして、汎用コードDictionary.TryGetValueはこれをどのように処理しますか?

この時点で、その「貧しい人のタイプかもしれない」は落ちます。直面する課題は、nullable参照型(NRT)を使用する場合、コンパイラがFoo<T>null不可として扱うことです。しかし、それを試して変更するとFoo<T?>Tnull許容値型としてクラスまたは構造体に制約されることは、CLRの観点とは非常に異なることになるでしょう。これにはさまざまな回避策があります。

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


2
TryParse(someString) is {} myClass-この構文は慣れるのに多少時間がかかりますが、私はこのアイデアが好きです。
セバスチャンレッド

TryParse(someString) is var myClass簡単に見えます。
オリビエジャコットデスコンブ

2
@ OlivierJacot-Descombesそれは簡単に見えるかもしれません...しかし、それは動作しません。車のパターンは常に一致するためx is var yxnull かどうかに関係なく常にtrueになります。
デビッドアルノ

15

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

詳細:


3

ここには矛盾はないと思います。

あなたの反対

public static bool TryParse(string s, out MyClass? result);

操作が成功したときにのみブランチに入るため、そのブランチで結果がnullになることはありません。

ただし、実際には、古いスタイルのTryParse関数のoutパラメーターへの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);

私は考えていないFirstOrDefault、その戻り値のnullであるかどうかがあるため、比較することができている主信号。ではTryParseメソッド、戻り値がtrueの場合はnullをされていないアウトパラメータは、メソッドの契約の一部です。
セバスチャンレッド

契約の一部ではありません。唯一保証されているのは、outパラメーターに何かが割り当てられていることです
Ewan

TryParseメソッドに期待する動作です。これIPAddress.TryParseまでtrueを返したが、その出力パラメータに非nullを割り当てなかった場合、バグとして報告します。
セバスチャンレッド

あなたの期待は理解できますが、コンパイラによって強制されません。だから、必ず、決して言わないかもしれないIPアドレスの仕様は真とnullを返しますが、私のJsonObjectの例が示す戻っnullは正しいかもしれない場合
ユアン・

「あなたの期待は理解できますが、コンパイラによって強制されるものではありません。」-私は知っていますが、私の質問は、他のコードの契約ではなく、私が念頭に置いている契約を表現するためのコードの書き方です。
セバスチャンレッド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.