2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Unity2021.3.1f1のc#でEnum型同士の比較で常にFalseを返すバグが。

Last updated at Posted at 2022-12-20

例えばこんなコードが

        Debug.Log((Enum)AnimationCullingType.AlwaysAnimate == (Enum)AnimationCullingType.AlwaysAnimate);
False

と返してくれちゃいます。

いろいろ書き方を変えてみると

        var v0 = (Enum)AnimationCullingType.AlwaysAnimate;
        var v1 = (Enum)AnimationCullingType.AlwaysAnimate;
        var v2 = AnimationCullingType.AlwaysAnimate;
        var v3 = AnimationCullingType.AlwaysAnimate;
        Debug.Log(v0 == v1);
        Debug.Log(v2 == v3);
        Debug.Log(v0.Equals(v1));
        Debug.Log(v0.Equals(AnimationCullingType.AlwaysAnimate));
        Debug.Log(v0 == (Enum)AnimationCullingType.AlwaysAnimate);
        Debug.Log((int)(object)v0 == (int)AnimationCullingType.AlwaysAnimate);
False
True
True
True
False
True

こんな感じ。

Enum型にするとボックス化が発生するので通常は使わないのですが、
c#のジェネリックでの制限から使わざるを得ない状況がわりとあります。

そんな状況で比較をするとハマったと。。

Equalsで比較すると問題ないのでインタプリタの単純なバグだとは思うのですが、こういう挙動のバージョンがリリースされている以上常に対策を入れる必要がありそうです。

これがVisualStudioでデバッグしているとデバッガのインライン評価では正しく出るのにステップ実行をすると挙動が異なるという厄介な問題が出ます。

最適化をかけた結果とかなら理解できるけれどエディタ拡張を書いててこんな言語レベルの非互換性に遭遇するとは。。

気になって古いバージョンでも確認したら2018でも再現。。昔からこうだったんですね。
何か言語的な歴史でもあるのでしょうか。

コメントをうけて確認したところ==とEqualsは別物である事が判明!

参照型に対して==もEqualsもデフォルトは参照比較になりますが、
オーバーライドはそれぞれ独立に行うことができ、
c#的には==は参照比較、Equalsが値の比較を期待するというもののようです。

私は==が内部的にEqualsを呼んでいると勘違いしていたのが間違いでした。

で、問題はUnityにVisualStudio2017のデバッガを接続してステップ実行したとき
デバッガ上で間違った結果を表示してくれる事こそがバグだったようです!!

まぁ仕様を正しく理解していなかった私もバグってたわけで。。

私は参照型でも値型に準じた扱いをしたい場合はEqualsと==を共にオーバーライドして
値の比較をするようにしたりしていましたが、人のコードにそれを期待するのは無理なので
安全に組むには==は使わないで明示的にEqualsとReferenceEqualsを使い分けるのがよさそうです。
参照比較を行いたいときは常にReferenceEqualsを使っていましたが足りてませんでしたね。

しかしEnumなんて参照型としての比較にはまったく意味がないんだし
値型としての扱いを期待するものなんだから==も値比較にしていてほしかったです。。
あと(int)へのキャストもね。。

Type型なんかはボックス化ではなく常に同じインスタンスを返してくれるので
参照型の==比較でも型の比較が行えて値型のように見える使い方ができますよね。

いっそ==を参照型に使ったら警告を出すオプションが欲しい。。

追記

よく考えたら==はstaticにしか宣言できなくってvirtualにできないわけで、
それのデフォルト実装がEqualsを呼んでくれていないとなると
継承関係によってさらに混乱が生じかねないんですね。。

追記2

デバッガでの不具合について、念のためもう少し調べてみたら
おかしいのはEnum型に対してのみでした。
普通のclassに対してはきちんと==が未定義の場合参照比較、
定義済みならその定義コードが正しくインプレース評価でも表示されました。
Enum型の時のみ何故か内包するenumの値による値比較になってるんですね。

問題を整理しておくと見た目の挙動では
・EnumはEquals(object)のみオーバーライドし==はデフォルトの参照比較のまま
・==のデフォルト実装は参照比較、Equalsと独立しているためEqualsのみを書き換えると挙動が食い違う事に。
・Unity経由でVisualStudio2019に接続した場合、Enumの==のデバッガ上での評価が値比較になるバグ?がある。(.Netコンソールだと大丈夫、unityでも普通のclassで間違えることはない)

==もEqualsもユーザーによるオーバライドが可能なので期待したものと必ずしも一致しないのは仕方ないのだけれど、
デフォルト実装がそれを助長する仕組みなのはある意味仕様バグともいえるような。
==のデフォルトでEqualsを呼んでくれてるのが一番素直だった気がします
今更かえられないけれど。

Enumは何故かEquals(object)しかオーバーライドしてないのも面倒・・
パフォーマンス的に読みやすさ的にもEquals(Enum)と==(Enum,Enum),==(Enum,int),(int)くらいは用意しておいてほしかったかも。

C#って楽でそこそこ便利なんだけれど変なところで罠があったりしますよね。
そんなときはデバッガ頼りなんだけれどデバッガの挙動まで期待と違ってしまうとほんとしんどい。

2
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?