Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9749

等値演算子のオーバーロードで null にハマった

$
0
0

C# で等値演算子をオーバーロードしたら null が引数に渡されたときハマってしまったので、その流れや解決策をメモ。

確認

まず、 C# では以下のようにして等値演算子(==)をオーバーロードすることができる。

publicstaticbooloperator==(Persona,Personb){returna.Equals(b);}

しかしこのサンプルコードでは、引数 a が null のとき NullReferenceExceptionが発生する。

ここで正常な挙動を確認すると、以下のようになっている。

// Random クラスを用いているのに特に意味はない.Randomr1=null;Randomr2=null;Console.WriteLine(r1==r2);// TrueConsole.WriteLine(r1.Equals(r2));// NullReferenceException

このことから、サンプルコードのように愚直に Equalsメソッドを呼び出すわけにはいかない。

試行

では以下のようにしてみるとどうだろう。

publicstaticbooloperator==(Persona,Personb){if(a==null){returnb==null;}else{returna.Equals(b);}}

このコードは、実行してみれば分かるが、引数 a、b の内容によらず StackOverflowExceptionが発生する。
それは、a や b が null かどうかを判定する条件式で等値演算子を使うことによって、無限ループ(無限再帰)を作り出しているためである。

解決策

再帰呼び出しを回避しつつ、null 判定をすることができるのが、 ReferenceEqualsメソッドである。
ReferenceEqualsメソッドは 参照の等価性を評価するため、null の変数は参照も null であるから、正しく判定をすることができる。

Randomr1=null;Randomr2=null;Console.WriteLine(ReferenceEquals(r1,null));// TrueConsole.WriteLine(ReferenceEquals(r2,null));// TrueConsole.WriteLine(ReferenceEquals(r1,r2));// True

以上のことから、等値演算子をオーバーロードする際には、以下のようにすると良い。

publicstaticbooloperator==(Persona,Personb){if(ReferenceEquals(a,null)){returnReferenceEquals(b,null);}else{returna.Equals(b);}}

非等値演算子

サンプルコードでは省いていたが、等値演算子をオーバーロードするときには、非等値演算子も同時にオーバーロードする必要がある。
ここでは再帰にならないため、以下のように簡潔に実装することができる。

publicstaticbooloperator!=(Persona,Personb){return!(a==b);}

おまけ(Equals)

Equalsメソッドをオーバーライドするときにも、同じ方法を使ったほうが良い。
等値演算子をオーバーロードしている状態で以下のように実装すると、引数 obj が null でないとき、何度か再帰呼び出しをすることになる。

publicoverrideboolEquals(objectobj){Personperson=objasPerson;if(person==null){returnfalse;}else{returnperson.Id==this.Id;}}

無限ループに陥ることはないので問題はないが、以下のようにして回避することができる。

publicoverrideboolEquals(objectobj){Personperson=objasPerson;if(ReferenceEquals(person,null)){returnfalse;}else{returnperson.Id==this.Id;}}

または、そもそも as 演算子ではなく is 演算子を使うことでも回避できる。
(記事を書いていて気づいた)

publicoverrideboolEquals(objectobj){if(objisPersonperson){returnperson.Id==this.Id;}else{returnfalse;}}

まとめ

hoge == nullではなく ReferenceEquals(hoge, null)を使おう。

参考

https://docs.microsoft.com/dotnet/api/system.object.referenceequals
https://docs.microsoft.com/dotnet/csharp/language-reference/operators/equality-operators
https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type


Viewing all articles
Browse latest Browse all 9749

Trending Articles