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