null参照が可能ではないように思われるときに、なぜ参照解除null参照の警告が表示されるのですか?


9

HNQでこの質問を読んだ後、C#8のNullable参照型について読み、いくつかの実験を行いました。

誰かが「コンパイラのバグを見つけた!」と言ったとき、私は10回のうち9回、またはもっと頻繁にそれを知っています。これは実際には仕様によるものであり、彼ら自身の誤解です。そして、私がこの機能を調べ始めたのは今日だけなので、明らかに私はそれをあまりよく理解していません。これが邪魔にならないように、このコードを見てみましょう:

#nullable enable
class Program
{
    static void Main()
    {
        var s = "";
        var b = s == null; // If you comment this line out, the warning on the line below disappears
        var i = s.Length; // warning CS8602: Dereference of a possibly null reference
    }
}

上記にリンクしたドキュメンテーションを読んだ後、私はそのs == null行が私に警告を与えると期待します—結局のところsnull可能でnullはないので、それを比較しても意味がありません。

代わりに、次の行に警告が表示され、警告はsnull参照の可能性があることを示していますが、人間にとってはそうではないことは明らかです。

もっとオーバー、警告はされていない、我々は比較しない場合に表示sしますnull

私はいくつかグーグルを実行し、GitHubの問題にぶつかりまし。これはまったく別の問題であることが判明しましたが、その過程で、この動作についてさらに洞察を与える寄稿者と会話しました(たとえば、「ヌルチェックは多くの場合、有用な方法です変数のNULL可能性に関する以前の推論をリセットするようコンパイラーに指示する方法。」)。しかし、それでもまだ主な質問には答えられませんでした。

新しいGitHubの問題を作成し、プロジェクトの非常に忙しいコントリビューターの時間を費やす可能性があるのではなく、これをコミュニティに公開します。

何が起こっているのか、その理由を教えてください。特に、s == nullラインで警告が生成されないのはなぜ ですか。また、ここで参照が可能CS8602ではないように思われるのに、なぜ表示さnullれるのでしょうか。リンクされたGitHubスレッドが示唆しているように、null可能性の推論が完全ではない場合、どのようにして失敗するのでしょうか?その例は何でしょうか?


コンパイラ自体がこの時点で変数「s」がnullになる可能性がある動作を設定しているようです。とにかく、文字列やオブジェクトを使用する場合は、関数を呼び出す前に必ずチェックする必要があります。「s?.Length」はトリックを実行し、警告自体は消えるはずです。
CHG

1
@chg、はnullにできない?ため、必要sはありません。と比較するのに愚かだったからといって、null可能にはなりませんnull
Andrew Savinykh

私は以前の質問(申し訳ありませんが、それを見つけることができません)を行っていましたが、値がnullであるというチェックを追加すると、コンパイラはそれが「null 」である可能性があることを「ヒント」として受け取ります。明らかにそうではありません。
stuartd

@stuartd、うん、それはそれがそうであるように見えるものです。だから今問題は:これはなぜ便利なのですか?
Andrew Savinykh

1
@chg、まあ、それは私が質問本文で言うことですよね?
Andrew Savinykh

回答:


7

これは事実上、@ stuartdがリンクした回答の複製であるため、ここではこれ以上詳しく説明しません。しかし、問題の根本は、これが言語のバグでもコンパイラのバグでもないということですが、実装されたとおりの意図された動作です。変数のnull状態を追跡します。最初に変数を宣言するとき、その状態はNotNullです。これは、nullではない値で明示的に初期化するためです。ただし、そのNotNullがどこから来たかは追跡しません。たとえば、これは実質的に同等のコードです。

#nullable enable
class Program
{
    static void Main()
    {
        M("");
    }
    static void M(string s)
    {
        var b = s == null;
        var i = s.Length; // warning CS8602: Dereference of a possibly null reference
    }
}

どちらの場合も、明示的にをテストsnullます。Madsがこの質問で答えたように、これをフロー分析への入力として使用します:https : //stackoverflow.com/a/59328672/2672518。その答えでは、結果として、返品時に警告が表示されます。この場合の答えは、nullの可能性がある参照を逆参照したという警告が表示されることです。

と比較するのに愚かだったからといって、null可能にはなりませんnull

うん、それは実際にはありません。コンパイラに。人間としては、このコードを見て、null参照例外をスローできないことは明らかです。しかし、ヌル可能フロー分析がコンパイラーに実装される方法は、それができません。値がどこから来たかに基づいて追加の状態を追加するこの分析のいくつかの改善点について説明しましたが、これにより、実装が大幅に複雑になるので、大きな利益は得られなかったと判断しました。これは、ユーザーが変数をa newまたは定数値で初期化し、それをnullとにかくチェックするような場合に便利です。


ありがとうございました。これは主に他の質問との類似点に対処します。違いにもっと対処したいと思います。たとえば、なぜs == null警告を出さないのですか?
Andrew Savinykh

また、私はそれを#nullable enable; string s = "";s = null;有効化されたnullアノテーションコンテキストでnullを「null不可の参照」に割り当てることを可能にする実装の利点は何ですか。
Andrew Savinykh

マッドの答えは、「[コンパイラ]が個別の変数の状態間の関係を追跡しない」ということに焦点を当てています。この例では個別の変数がないため、マッドの残りの答えをこのケースに適用するのは困難です。
Andrew Savinykh

言うまでもありませんが、私は批評のためではなく、学ぶためにここにいます。2001年に初めて登場したときからC#を使用しています。私はその言語に不慣れではありませんが、コンパイラーの動作は驚くべきものでした。この質問の目的は、なぜこの動作が人間に役立つのかという論理的根拠を明らかにすることです。
Andrew Savinykh

チェックする正当な理由がありますs == null。たとえば、パブリックメソッドを使用していて、パラメーターの検証を行いたいとします。または、誤って注釈が付けられたライブラリを使用している可能性があり、そのバグが修正されるまで、宣言されていない場所でnullを処理する必要があります。これらのどちらの場合でも、警告を出せば、悪い経験になるでしょう。割り当てを許可する場合については、ローカル変数アノテーションは読み取り専用です。ランタイムにはまったく影響しません。実際、コードのチャーンを減らしたい場合は警告をオフにできるように、すべての警告を1つのエラーコードに入れています。
333fred

0

null可能性の推論が完全なものではない場合、[..]どのようにして失敗するのでしょうか?

C#8のnull可能な参照が利用可能になり次第、喜んで採用しました。私はReSharperの[NotNull](など)表記を使用するのに慣れていたので、2つの違いがいくつかありました。

C#コンパイラーはだまされる可能性がありますが、注意怠る傾向があります(通常、常にではありません)。

将来の訪問者のための参考として、これらはコンパイラがかなり混乱しているのを私が見たシナリオです(私はこれらすべてのケースが設計によると仮定します):

  • nullはnullを許容します。逆参照警告を回避するためによく使用されますが、オブジェクトをnullにできないようにします。足を2つに留めておきたいようです。
    string s = null!; //No warning

  • 表面分析。ReSharperとは対照的に(コードアノテーションを使用してそれを行います)、C#コンパイラはnull可能な参照を処理するための属性の全範囲をまだサポートしていません。
    void DoSomethingWith(string? s)
    {    
        ThrowIfNull(s);
        var split = s.Split(' '); //Dereference warning
    }

ただし、警告を取り除くために、いくつかの構文を使用してnullが可能かどうかを確認できます。

    public static void DoSomethingWith(string? s)
    {
        Debug.Assert(s != null, nameof(s) + " != null");
        var split = s.Split(' ');  //No warning
    }

または(まだかなりクール)属性(ここですべてを検索):

    public static bool IsNullOrEmpty([NotNullWhen(false)] string? value)
    {
        ...
    }

  • 影響を受けやすいコード分析。これがあなたが明らかにしたものです。コンパイラーは、機能するために仮定を立てる必要があり、(少なくとも人間にとって)直感に反するように見える場合があります。
    void DoSomethingWith(string s)
    {    
        var b = s == null;
        var i = s.Length; // Dereference warning
    }

  • ジェネリックの問題ここで質問さ、非常によく説明されました(以前と同じ記事、「Tの問題?」)。ジェネリックは、参照と値の両方を満足させる必要があるため、複雑です。主な違いは、string?は単なる文字列ですが、にint?なりNullable<int>、コンパイラーにそれらを実質的に異なる方法で処理させることです。また、ここでは、コンパイラーが安全なパスを選択しているため、期待しているものを指定する必要があります。
    public interface IResult<out T> : IResult
    {
        T? Data { get; } //Warning/Error: A nullable type parameter must be known to be a value type or non-nullable reference type.
    }

制約を与えて解決:

    public interface IResult<out T> : IResult where T : class { T? Data { get; }}
    public interface IResult<T> : IResult where T : struct { T? Data { get; }}

ただし、制約を使用せずに「?」を削除した場合 Dataからは、 'default'キーワードを使用してnull値を入れることができます。

    [Pure]
    public static Result<T> Failure(string description, T data = default)
        => new Result<T>(ResultOutcome.Failure, data, description); 
        // data here is definitely null. No warning though.

安全ではないコードを書くことができるので、最後の方は私にとってトリッキーなようです。

これが誰かを助けることを願っています。


null可能な属性に関するドキュメントを読むことをお勧めします:docs.microsoft.com/en-us/dotnet/csharp/nullable-attributes。彼らはあなたの問題のいくつか、特に表面分析セクションとジェネリックスを使って解決します。
333fred

リンク@ 333fredをありがとう。試すことができる属性はあるものの、投稿した問題を解決する属性(null ThrowIfNull(s);sはないことを私に保証するもの)は存在しません。また、記事では、ハンドラ方法について説明します非NULL可能私はあなたがヌル値ではなく、それについては警告を持つ、コンパイラを「だます」ことができますどのように見せていた一方で、ジェネリックを。
Alvin Sartor

実際には、属性は存在します。私はそれを追加するためにドキュメントにバグを提出しました。あなたが探していDoesNotReturnIf(bool)ます。
333フレッド

@ 333fredは実際にもっと似DoesNotReturnIfNull(nullable)たものを探しています。
Alvin Sartor
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.