NullReferenceException とは何ですか? また、それを修正するにはどうすればいいですか? 質問する

NullReferenceException とは何ですか? また、それを修正するにはどうすればいいですか? 質問する

いくつかのコードがあり、それを実行すると、次のような がスローされますNullReferenceException

オブジェクト参照がオブジェクトのインスタンスに設定されていません。

これはどういう意味ですか? また、このエラーを修正するにはどうすればいいですか?

ベストアンサー1

原因は何ですか?

結論

null(またはVB.NET では)を使用しようとしていますNothing。 つまり、 に設定するnullか、何も設定していないかのどちらかです。

他のものと同様、null渡されます。メソッド「A」null ある場合、メソッド「B」がメソッド「A」null 渡した可能性があります。

nullさまざまな意味を持つことがあります:

  1. 初期化されていないため何も指していないオブジェクト変数。この場合、そのようなオブジェクトのメンバーにアクセスすると、が発生しますNullReferenceException
  2. 開発者は意図的にを使用していますnull。これは、意味のある値が利用できないことを示すためです。C # には、変数に対して null 許容データ型の概念があることに注意してください (データベース テーブルに null 許容フィールドがあるのと同様)。nullこれらに代入して、そこに値が格納されていないことを示すことができます。たとえば、int? a = null;(これは のショートカットですNullable<int> a = null;) では、疑問符はnull変数 に格納できることを示しています。これは、 または を使用してa確認できます。この例のように、null 許容変数では、明示的に を介して値にアクセスすることも、通常どおり を介してアクセスすることもできます。 を介してアクセスすると、場合ではなくがスローされることに注意してください事前に確認する必要があります。つまり、別の null 非許容変数がある場合は、またはより短い のように代入する必要がありますif (a.HasValue) {...}if (a==null) {...}aa.Valuea
    a.ValueInvalidOperationExceptionNullReferenceExceptionanullint b;if (a.HasValue) { b = a.Value; }if (a != null) { b = a; }

この記事の残りの部分では、さらに詳しく説明し、多くのプログラマーが犯しがちな、 につながる可能性のある間違いを示しますNullReferenceException

すなわち

runtimeをスローすることは常にNullReferenceException 同じことを意味します。つまり、参照を使用しようとしていますが、参照が初期化されていません (または、一度初期化されたが、現在は初期化されていません)。

これは、参照が でありnull、参照を介してメンバー(メソッドなど)にアクセスできないことを意味しますnull。最も単純なケース:

string foo = null;
foo.ToUpper();

を指す参照に対してNullReferenceExceptionインスタンス メソッドを呼び出すことはできないため、2 行目でがスローされますToUpper()stringnull

デバッグ

のソースをどのように見つけますかNullReferenceException?例外自体を見ることとは別に、例外は発生した場所で正確にスローされますが、Visual Studioでのデバッグの一般的なルールが適用されます。戦略的にブレークポイントを配置し、変数を検査する名前の上にマウスを置くか、(クイック)ウォッチ ウィンドウを開くか、ローカルや自動などのさまざまなデバッグ パネルを使用することによって実行できます。

参照が設定されている場所または設定されていない場所を確認するには、その名前を右クリックして [すべての参照を検索] を選択します。次に、見つかったすべての場所にブレークポイントを配置し、デバッガーを接続した状態でプログラムを実行します。デバッガーがこのようなブレークポイントで中断するたびに、参照が null でないことを予期しているかどうかを判断し、変数を調べて、予期したインスタンスを指していることを確認する必要があります。

このようにプログラム フローをたどることで、インスタンスが null であってはならない場所と、それが適切に設定されない理由を見つけることができます。

例外がスローされる可能性がある一般的なシナリオは次のとおりです。

ジェネリック

ref1.ref2.ref3.member

ref1 または ref2 または ref3 が null の場合、 が返されますNullReferenceException。この問題を解決するには、式をより単純な式に書き直して、どれが null であるかを調べます。

var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member

具体的には、 ではHttpContext.Current.User.Identity.Name、 がHttpContext.Currentnull になるか、Userプロパティが null になるか、Identityプロパティが null になる可能性があります。

間接的

public class Person 
{
    public int Age { get; set; }
}
public class Book 
{
    public Person Author { get; set; }
}
public class Example 
{
    public void Foo() 
    {
        Book b1 = new Book();
        int authorAge = b1.Author.Age; // You never initialized the Author property.
                                       // there is no Person to get an Age from.
    }
}

子 (Person) の null 参照を回避したい場合は、親 (Book) オブジェクトのコンストラクターで初期化することができます。

ネストされたオブジェクト初期化子

ネストされたオブジェクト初期化子にも同じことが当てはまります。

Book b1 = new Book 
{ 
   Author = { Age = 45 } 
};

これは次のように翻訳されます:

Book b1 = new Book();
b1.Author.Age = 45;

キーワードが使用されている間はnew、 の新しいインスタンスのみが作成されBook、 の新しいインスタンスは作成されないPersonため、Authorプロパティは のままですnull

ネストされたコレクション初期化子

public class Person 
{
    public ICollection<Book> Books { get; set; }
}
public class Book 
{
    public string Title { get; set; }
}

ネストされたコレクションはInitializers同じように動作します。

Person p1 = new Person 
{
    Books = {
         new Book { Title = "Title1" },
         new Book { Title = "Title2" },
    }
};

これは次のように翻訳されます:

Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });

new Personは のインスタンスを作成するだけですPersonBooksコレクションは のままですnull。コレクションInitializer構文は のコレクションを作成せずp1.Books、 文に変換するだけですp1.Books.Add(...)

配列

int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.

配列要素

Person[] people = new Person[5];
people[0].Age = 20 // people[0] is null. The array was allocated but not
                   // initialized. There is no Person to set the Age for.

ギザギザの配列

long[][] array = new long[1][];
array[0][0] = 3; // is null because only the first dimension is yet initialized.
                 // Use array[0] = new long[2]; first.

コレクション/リスト/辞書

Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
                               // There is no Dictionary to perform the lookup.

範囲変数(間接/遅延)

public class Person 
{
    public string Name { get; set; }
}
var people = new List<Person>();
people.Add(null);
var names = from p in people select p.Name;
string firstName = names.First(); // Exception is thrown here, but actually occurs
                                  // on the line above.  "p" is null because the
                                  // first element we added to the list is null.

イベント (C#)

public class Demo
{
    public event EventHandler StateChanged;
    
    protected virtual void OnStateChanged(EventArgs e)
    {        
        StateChanged(this, e); // Exception is thrown here 
                               // if no event handlers have been attached
                               // to StateChanged event
    }
}

(注: VB.NET コンパイラはイベントの使用に対して null チェックを挿入するため、VB.NET でイベントをチェックする必要はありませんNothing。)

不適切な命名規則:

フィールドにローカルとは異なる名前を付けた場合、フィールドが初期化されていないことに気付くかもしれません。

public class Form1
{
    private Customer customer;
    
    private void Form1_Load(object sender, EventArgs e) 
    {
        Customer customer = new Customer();
        customer.Name = "John";
    }
    
    private void Button_Click(object sender, EventArgs e)
    {
        MessageBox.Show(customer.Name);
    }
}

これは、フィールドの前にアンダースコアを付けるという規則に従うことで解決できます。

    private Customer _customer;

ASP.NET ページ ライフ サイクル:

public partial class Issues_Edit : System.Web.UI.Page
{
    protected TestIssue myIssue;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
             // Only called on first load, not when button clicked
             myIssue = new TestIssue(); 
        }
    }
        
    protected void SaveButton_Click(object sender, EventArgs e)
    {
        myIssue.Entry = "NullReferenceException here!";
    }
}

ASP.NET セッション値

// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();

ASP.NET MVC の空のビュー モデル

If the exception occurs when referencing a property of @Model in an ASP.NET MVC View, you need to understand that the Model gets set in your action method, when you return a view. When you return an empty model (or model property) from your controller, the exception occurs when the views access it:

// Controller
public class Restaurant:Controller
{
    public ActionResult Search()
    {
        return View();  // Forgot the provide a Model here.
    }
}

// Razor view 
@foreach (var restaurantSearch in Model.RestaurantSearch)  // Throws.
{
}
    
<p>@Model.somePropertyName</p> <!-- Also throws -->

WPF Control Creation Order and Events

WPF controls are created during the call to InitializeComponent in the order they appear in the visual tree. A NullReferenceException will be raised in the case of early-created controls with event handlers, etc., that fire during InitializeComponent which reference late-created controls.

For example:

<Grid>
    <!-- Combobox declared first -->
    <ComboBox Name="comboBox1" 
              Margin="10"
              SelectedIndex="0" 
              SelectionChanged="comboBox1_SelectionChanged">
       <ComboBoxItem Content="Item 1" />
       <ComboBoxItem Content="Item 2" />
       <ComboBoxItem Content="Item 3" />
    </ComboBox>
        
    <!-- Label declared later -->
    <Label Name="label1" 
           Content="Label"
           Margin="10" />
</Grid>

Here comboBox1 is created before label1. If comboBox1_SelectionChanged attempts to reference `label1, it will not yet have been created.

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    label1.Content = comboBox1.SelectedIndex.ToString(); // NullReferenceException here!!
}

Changing the order of the declarations in the XAML (i.e., listing label1 before comboBox1, ignoring issues of design philosophy) would at least resolve the NullReferenceException here.

Cast with as

var myThing = someObject as Thing;

This doesn't throw an InvalidCastException but returns a null when the cast fails (and when someObject is itself null). So be aware of that.

LINQ FirstOrDefault() and SingleOrDefault()

The plain versions First() and Single() throw exceptions when there is nothing. The "OrDefault" versions return null in that case. So be aware of that.

foreach

foreach throws when you try to iterate on a null collection. Usually caused by unexpected null result from methods that return collections.

List<int> list = null;    
foreach(var v in list) { } // NullReferenceException here

More realistic example - select nodes from XML document. Will throw if nodes are not found but initial debugging shows that all properties valid:

foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))

Ways to Avoid

Explicitly check for null and ignore null values.

If you expect the reference sometimes to be null, you can check for it being null before accessing instance members:

void PrintName(Person p)
{
    if (p != null) 
    {
        Console.WriteLine(p.Name);
    }
}

Explicitly check for null and provide a default value.

Methods you call expecting an instance can return null, for example when the object being sought cannot be found. You can choose to return a default value when this is the case:

string GetCategory(Book b) 
{
    if (b == null)
        return "Unknown";
    return b.Category;
}

Explicitly check for null from method calls and throw a custom exception.

You can also throw a custom exception, only to catch it in the calling code:

string GetCategory(string bookTitle) 
{
    var book = library.FindBook(bookTitle);  // This may return null
    if (book == null)
        throw new BookNotFoundException(bookTitle);  // Your custom exception
    return book.Category;
}

Use Debug.Assert if a value should never be null, to catch the problem earlier than the exception occurs.

When you know during development that a method could, but never should return null, you can use Debug.Assert() to break as soon as possible when it does occur:

string GetTitle(int knownBookID) 
{
    // You know this should never return null.
    var book = library.GetBook(knownBookID);  

    // Exception will occur on the next line instead of at the end of this method.
    Debug.Assert(book != null, "Library didn't return a book for known book ID.");

    // Some other code

    return book.Title; // Will never throw NullReferenceException in Debug mode.
}

Though this check will not end up in your release build, causing it to throw the NullReferenceException again when book == null at runtime in release mode.

Use GetValueOrDefault() for nullable value types to provide a default value when they are null.

DateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the default value provided (DateTime.Now), because appointment is null.

appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the appointment date, not the default

Use the null coalescing operator: ?? [C#] or If() [VB].

The shorthand to providing a default value when a null is encountered:

IService CreateService(ILogger log, Int32? frobPowerLevel)
{
   var serviceImpl = new MyService(log ?? NullLog.Instance);
 
   // Note that the above "GetValueOrDefault()" can also be rewritten to use
   // the coalesce operator:
   serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}

Use the null condition operator: ?. or ?[x] for arrays (available in C# 6 and VB.NET 14):

This is also sometimes called the safe navigation or Elvis (after its shape) operator. If the expression on the left side of the operator is null, then the right side will not be evaluated, and null is returned instead. That means cases like this:

var title = person.Title.ToUpper();

If the person does not have a title, this will throw an exception because it is trying to call ToUpper on a property with a null value.

In C# 5 and below, this can be guarded with:

var title = person.Title == null ? null : person.Title.ToUpper();

Now the title variable will be null instead of throwing an exception. C# 6 introduces a shorter syntax for this:

var title = person.Title?.ToUpper();

This will result in the title variable being null, and the call to ToUpper is not made if person.Title is null.

Of course, you still have to check title for null or use the null condition operator together with the null coalescing operator (??) to supply a default value:

// regular null check
int titleLength = 0;
if (title != null)
    titleLength = title.Length; // If title is null, this would throw NullReferenceException
    
// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;

Likewise, for arrays you can use ?[i] as follows:

int[] myIntArray = null;
var i = 5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");

This will do the following: If myIntArray is null, the expression returns null and you can safely check it. If it contains an array, it will do the same as: elem = myIntArray[i]; and returns the ith element.

Use null context (available in C# 8):

Introduced in C# 8, null contexts and nullable reference types perform static analysis on variables and provide a compiler warning if a value can be potentially null or have been set to null. The nullable reference types allow types to be explicitly allowed to be null.

The nullable annotation context and nullable warning context can be set for a project using the Nullable element in your csproj file. This element configures how the compiler interprets the nullability of types and what warnings are generated. Valid settings are:

  • enable: The nullable annotation context is enabled. The nullable warning context is enabled. Variables of a reference type, string, for example, are non-nullable. All nullability warnings are enabled.
  • disable: The nullable annotation context is disabled. The nullable warning context is disabled. Variables of a reference type are oblivious, just like earlier versions of C#. All nullability warnings are disabled.
  • safeonly: The nullable annotation context is enabled. The nullable warning context is safeonly. Variables of a reference type are non-nullable. All safety nullability warnings are enabled.
  • warnings: The nullable annotation context is disabled. The nullable warning context is enabled. Variables of a reference type are oblivious. All nullability warnings are enabled.
  • safeonlywarnings: The nullable annotation context is disabled. The nullable warning context is safeonly. Variables of a reference type are oblivious. All safety nullability warnings are enabled.

A nullable reference type is noted using the same syntax as nullable value types: a ? is appended to the type of the variable.

Special techniques for debugging and fixing null derefs in iterators

C# supports "iterator blocks" (called "generators" in some other popular languages). NullReferenceException can be particularly tricky to debug in iterator blocks because of deferred execution:

public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    for (int i = 0; i < count; ++i)
    yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable<Frobs> frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }

If whatever results in null then MakeFrob will throw. Now, you might think that the right thing to do is this:

// DON'T DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
   for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}

Why is this wrong? Because the iterator block does not actually run until the foreach! The call to GetFrobs simply returns an object which when iterated will run the iterator block.

By writing a null check like this you prevent the NullReferenceException, but you move the NullArgumentException to the point of the iteration, not to the point of the call, and that is very confusing to debug.

The correct fix is:

// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   // No yields in a public method that throws!
   if (f == null) 
       throw new ArgumentNullException("f", "factory must not be null");
   return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
   // Yields in a private method
   Debug.Assert(f != null);
   for (int i = 0; i < count; ++i)
        yield return f.MakeFrob();
}

That is, make a private helper method that has the iterator block logic and a public surface method that does the null check and returns the iterator. Now when GetFrobs is called, the null check happens immediately, and then GetFrobsForReal executes when the sequence is iterated.

If you examine the reference source for LINQ to Objects you will see that this technique is used throughout. It is slightly more clunky to write, but it makes debugging nullity errors much easier. Optimize your code for the convenience of the caller, not the convenience of the author.

A note on null dereferences in unsafe code

C# has an "unsafe" mode which is, as the name implies, extremely dangerous because the normal safety mechanisms which provide memory safety and type safety are not enforced. You should not be writing unsafe code unless you have a thorough and deep understanding of how memory works.

In unsafe mode, you should be aware of two important facts:

  • dereferencing a null pointer produces the same exception as dereferencing a null reference
  • dereferencing an invalid non-null pointer can produce that exception in some circumstances

To understand why that is, it helps to understand how .NET produces NullReferenceException in the first place. (These details apply to .NET running on Windows; other operating systems use similar mechanisms.)

ではメモリが仮想化されておりWindows、各プロセスはオペレーティング システムによって追跡される多数の「ページ」のメモリからなる仮想メモリ空​​間を取得します。メモリの各ページには、読み取り、書き込み、実行などの使用方法を決定するフラグが設定されています。最下位ページは、「何らかの方法で使用された場合はエラーを生成する」とマークされています。

の null ポインターと null 参照は、どちらもC#内部的には数値 0 として表されるため、対応するメモリ ストレージに逆参照しようとすると、オペレーティング システムによってエラーが発生します。.NET ランタイムはこのエラーを検出し、 に変換しますNullReferenceException

そのため、null ポインターと null 参照の両方を逆参照すると、同じ例外が生成されます。

2 番目の点についてはどうでしょうか。仮想メモリの最下位ページにある無効なポインターを逆参照すると、同じオペレーティング システム エラーが発生し、同じ例外が発生します。

なぜこれが意味をなすのでしょうか? 2 つの int と、null に等しいアンマネージ ポインターを含む構造体があるとします。構造体の 2 番目の int を逆参照しようとすると、位置 0 のストレージにアクセスしようとはせず、位置 4 のストレージにアクセスします。ただし、論理的には、null を介してCLRそのアドレスにアクセスしているため、これは null 逆参照です。

安全でないコードを扱っていて が発生した場合NullReferenceException、問題のあるポインターが null である必要はないことに注意してください。最下位ページの任意の場所にある可能性があり、この例外が生成されます。

おすすめ記事