構造体のクラスメンバーを持つC#8でnullの逆参照の可能性に関する警告が表示されないのはなぜですか?


8

null可能な参照型を有効にしたC#8プロジェクトで、次のコードを使用すると、null逆参照の可能性に関する警告が表示されるはずですが、表示されません。

public class ExampleClassMember
{
    public int Value { get; }
}

public struct ExampleStruct
{
    public ExampleClassMember Member { get; }
}

public class Program
{
    public static void Main(string[] args)
    {
        var instance = new ExampleStruct();
        Console.WriteLine(instance.Member.Value);  // expected warning here about possible null dereference
    }
}

場合はinstanceデフォルトコンストラクタで初期化され、instance.Memberデフォルト値に設定されてExampleClassMemberいます、null。したがって、実行時にinstance.Member.ValueがスローさNullReferenceExceptionれます。C#8のnull可能性の検出を理解しているので、この可能性についてコンパイラーの警告が表示されるはずですが、わかりません。何故ですか?


これをRoslyn GitHubリポジトリの問題として提出しましたか?

@Daiまだ(まだ)していません。それが正当なバグであり、私が見逃していないものではない場合は、それを行います。
DylanSp

FWIW、このコードはC#7.0でコンパイルできませ -自動プロパティの値を設定するためのコンストラクターがない2つの型に関するエラーが発生します。ただし、Roslyn 3.0および.NET Core 3.0コンパイラでコンパイルできます。実際、後者の場合はどちらもNREで実行されます。コンパイラオプションを設定する機能のないWebベースのIDEを使用しています。

変更すると、C#8.0コンパイラは警告を表示します ExampleStructから、を表示structclassます。

1
@tymtamプレビュー版です。リリース版では、Nullable
Panagiotis Kanavos '17 / 10/17

回答:


13

の呼び出しで警告が表示される理由はないことに注意してくださいConsole.WriteLine()。参照型プロパティはnull許容型ではないため、nullである可能性があることをコンパイラが警告する必要はありません。

コンパイラーstruct自体の参照についてコンパイラーが警告する必要があると主張するかもしれません。それは私には理にかなっているようです。しかし、そうではありません。これは、値型のデフォルトの初期化によって引き起こされる抜け穴のようです。つまり、常にすべてのフィールド(参照型フィールドの場合はnull、数値型の場合はゼロなど)を常にゼロにするデフォルト(パラメーターなし)コンストラクターが必要です。 )。

私はそれを抜け穴と呼びます。理論的には、null不可の参照値は実際には常にnull以外であるべきだからです。ああ。:)

この抜け穴は、このブログ記事「C#でのnull可能な参照型の紹介」で対処されているようです。

nullの回避これまでのところ、警告はnull可能な参照のnullが逆参照されないように保護することに関するものでした。コインの反対側は、null不可の参照にnullがまったく含まれないようにすることです。

null値が存在する可能性のある方法はいくつかあり、それらのほとんどは警告する価値がありますが、それらのうちのいくつかは、回避する方が良い別の「警告の海」を引き起こす可能性があり
ます。

  • nullを許容しない参照型のフィールドを持つ構造体のデフォルトのコンストラクターを使用する。デフォルトのコンストラクター(構造体をゼロにする)は暗黙的に多くの場所で使用できるため、これは卑劣です。おそらく警告しない方がいいでしょう [強調マイン-PD]。さもなければ、多くの既存の構造体タイプは役に立たなくなるでしょう。

つまり、これは抜け穴ですが、バグではありません。言語設計者はそれを認識していますが、このシナリオを警告から除外することを選択しました。struct初期化が機能です。

これは、この機能の背後にあるより広範な哲学と一致していることに注意してください。同じ記事から:

したがって、既存のコードについて文句を言う必要があります。しかし、不愉快なことではありません。ここでは、そのバランスをとる方法を説明します。

  1. すべての警告に反応して排除したとしても、ヌルの安全性は保証されません[強調-PD]。分析には必要に応じて多くの穴があり、一部は選択による穴もあります。

その最後のポイントまで:時には警告は「正しい」ことですが、実際にnullセーフな方法で書かれている場合でも、既存のコードでは常に発生します。そのような場合、正確さではなく、利便性の面で誤りを犯します。既存のコードで「警告の海」を生み出すことはできません。あまりに多くの人々が警告をオフにして、それから利益を得ることは決してありません。

また、この同じ問題が、名目上nullにできない参照型の配列(たとえばstring[])にも存在することに注意してください。配列を作成すると、すべての参照値はnull、これは正当であり、警告は生成されません。


なぜ物事が現状のままであるのかを説明するためにこれで終わりです。次に、問題はどうなりますか?それはもっと主観的です、そして私は正しいか間違った答えがあるとは思いません。つまり…

私は個人的にstructケースバイケースでタイプを扱います。意図するところのために実際にNULL可能参照型ですが、私は適用される?注釈を。そうでなければ、私はしません。

技術的には、aのすべての参照値はstruct「null可能」である必要があります。つまり?、型名にnull可能注釈を含めます。しかし、多くの同様の機能と同様に(C#での非同期/待機など)const C ++、これは「感染性」の側面があり、後でそのアノテーションをオーバーライドする!か、明示的なnullチェックを含める必要があります。、またはその値をnull可能な別の参照型変数に割り当てるだけです。

私にとって、これは、null許容の参照型を有効にする多くの目的を無効にします。そのようなstruct型のメンバーはいずれにせよ特別な場合の処理​​を必要とし、null不可の参照型を引き続き使用しながら本当に安全に処理する唯一の方法はstruct、を使用するすべての場所にnullチェックを配置することであるため、コードがを初期化するときstruct、それを正しく行い、null不可の参照型メンバーが実際にnull以外の値に初期化されるようにするのはコードの責任であるということを受け入れるのは、妥当な実装選択です。

これは、デフォルト以外のコンストラクタ(つまり、パラメータを持つコンストラクタ)やファクトリメソッドなどの初期化の「公式な」手段を提供することで支援できます。デフォルトのコンストラクタを使用するリスクは常に存在しますが、配列の割り当てのようにコンストラクタをまったく使用しないリスクがありますが、struct正しく、これを使用するコードが非null可能な変数。

つまり、null許容の参照型に関する100%の安全性が必要な場合は、その特定の目標に対する正しいアプローチは、常に、structwith 内のすべての参照型メンバーに注釈を付けること?です。これは、すべてのフィールドとすべての自動実装プロパティ、およびそのような値またはそのような値の積を直接返すメソッドまたはプロパティゲッターを意味します。次に、使用するコードには、そのような値がnullを許容しない変数にコピーされるすべてのポイントにnullチェックまたはnullを許容する演算子を含める必要があります。


良い分析、そしてそのブログ投稿を見つけてくれてありがとう-それはかなり決定的な答えです。
DylanSp

1

@ peter-dunihoによる優れた回答に照らして、2019年10月の時点で、すべての非値型メンバーにnull可能な参照をマークするのが最善のようです。

#nullable enable
public class C
{
    public int P1 { get; } 
}

public struct S
{
    public C? Member { get; } // Reluctantly mark as nullable reference because
                              // https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/
                              // states:
                              // "Using the default constructor of a struct that has a
                              // field of nonnullable reference type. This one is 
                              // sneaky, since the default constructor (which zeroes 
                              // out the struct) can even be implicitly used in many
                              // places. Probably better not to warn, or else many
                              // existing struct types would be rendered useless."
}

public class Program
{
    public static void Main()
    {
        var instance = new S();
        Console.WriteLine(instance.Member.P1); // Warning
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.