web-dev-qa-db-ja.com

構造体とnullの比較

可能性のある複製:
値の型をnullと比較しても問題ありません

マルチスレッド環境のWindowsアプリで作業していて、「ウィンドウハンドルが作成されるまで、コントロールでInvokeまたはBeginInvokeを呼び出せない」という例外が発生することがありました。だから私はこのコード行を追加するだけだと考えました:

if(this.Handle != null)
{
   //BeginInvokeCode
}

しかし、それは問題を解決しませんでした。それで、私はもう少し掘り下げて、IntPtr(Form.Handleがそうである型)がnullにできない構造体であることを理解しました。これはうまくいった修正でした:

if(this.Handle != IntPtr.Zero)
{
   //BeginInvokeCode
}

それでそれは私に当たりました、なぜ私がそれをヌルかどうかチェックしていたときにコンパイルしましたか?だから私は自分で試してみることにしました:

    public struct Foo { }

その後:

    static void Main(string[] args)
    {
        Foo f = new Foo();
        if (f == null) { }
    }

そして、「エラー1演算子 '=='は、タイプ 'ConsoleApplication1.Foo'および ''のオペランドには適用できない」と言ってコンパイルされなかったことは確かです。 OK、それから私はIntPtrのメタデータを調べ始め、IntPtr構造体(ISerializable、ComVisible)にあったFoo構造体にすべてを追加し始めましたが、何の助けにもなりませんでした。最後に、==と!=の演算子のオーバーロードを追加すると、機能しました。

[Serializable]
[ComVisible(true)]
public struct Foo : ISerializable
{
    #region ISerializable Members

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        throw new NotImplementedException();
    }

    #endregion

    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }

    public static bool operator ==(Foo f1, Foo f2) { return false; }
    public static bool operator !=(Foo f1, Foo f2) { return false; }
}

これは最終的にコンパイルされました:

    static void Main(string[] args)
    {
        Foo f = new Foo();
        if (f == null) { }
    }

私の質問はなぜですか? ==と!=をオーバーライドすると、nullと比較できるのはなぜですか? ==と!=へのパラメータはまだFoo型であり、nullにできないので、なぜこれが突然許可されたのですか?

37
BFree

問題は、MSがnull許容型を導入したときに、すべての構造体が暗黙的にnull許容型(foo?)に変換できるようにしたため、コードが

if( f == null)

に相当

if ( (Nullable<foo>)f == (Nullable<foo>)null) 

MSDNは「値型に存在するユーザー定義の演算子はnull許容型でも使用される可能性がある」と述べているため、operator==をオーバーライドすると、ユーザー定義= =-null可能なオーバーロードを無料で提供します。

余談:

あなたの例のように、いくつかのコンパイラの最適化がありますテストがあったことをほのめかすだけでコンパイラによって出力される唯一のものはこのILです:

ldc.i4.0
ldc.i4.0
ceq
stloc.1   //where there is an unused boolean local

Mainを

Foo f = new Foo();
object b = null;
if (f == b) { Console.WriteLine("?"); }

コンパイルできなくなりました。しかし、構造体をボックス化した場合:

Foo f = new Foo();
object b = null;
if ((object)f == b) { Console.WriteLine("?"); }

コンパイルしてILを出力し、期待どおりに実行する場合(構造体がnullになることはありません);

18
Philip Rieck

これはシリアライゼーションやCOMとは何の関係もないため、方程式から削除する価値があります。たとえば、問題を示す短いが完全なプログラムを次に示します。

using System;

public struct Foo
{
    // These change the calling code's correctness
    public static bool operator ==(Foo f1, Foo f2) { return false; }
    public static bool operator !=(Foo f1, Foo f2) { return false; }

    // These aren't relevant, but the compiler will issue an
    // unrelated warning if they're missing
    public override bool Equals(object x) { return false; }
    public override int GetHashCode() { return 0; }
}

public class Test
{
    static void Main()
    {
        Foo f = new Foo();
        Console.WriteLine(f == null);
    }
}

NullリテラルからNullable<Foo>への暗黙的な変換があり、あなたがcanをこれを合法的に行うため、これはコンパイルできると思います。

Foo f = new Foo();
Foo? g = null;
Console.WriteLine(f == g);

これは、==がオーバーロードされている場合にのみ発生するのは興味深いことです。以前、Marc Gravellがこれを発見しました。 実際にコンパイラのバグなのか、それとも変換、オーバーロードなどが解決される方法が非常に微妙なものなのかはわかりません。

someの場合(例:intdecimal)コンパイラは暗黙的な変換について警告しますが、それ以外の場合(例:Guid)は警告しますない.

6
Jon Skeet

==演算子のオーバーロードにより、コンパイラーに次のいずれかを選択できると思います。

public static bool operator ==(object o1, object o2)

そして

public static bool operator ==(Foo f1, Foo f2)

両方を選択することで、左をオブジェクトにキャストして前者を使用できます。確かに、コードに基づいて何かを実行しようとしても、オペレーターのオーバーロードに踏み込むことはありません。演算子の選択がないため、コンパイラはさらにいくつかのチェックを実行しています。

4
David M

演算子をオーバーロードすると、特定の演算子で必要なすべてのロジックを処理するという概念に明示的にサブスクライブしていると思います。したがって、演算子のオーバーロードメソッドでnullを処理する必要がある場合は、nullを処理する必要があります。この場合、おそらくnullと比較すると、オーバーロードされたメソッドがヒットしないことに気付いたと思います。

本当に興味深いのは、次のことです Henksはここに答えます 、リフレクターで次のコードをチェックアウトしました。

Foo f1 = new Foo();
if(f1 == null)
{
  Console.WriteLine("impossible");
}

Console.ReadKey();

これは反射板が示したものです。

Foo f1 = new Foo();
Console.ReadKey();

コンパイラはそれをクリーンアップするため、オーバーロードされた演算子メソッドが呼び出されることすらありません。

1
Stan R.

構造体は、オーバーロード "=="または "!="を定義していません。これが、元のエラーが発生した理由です。構造体にオーバーロードが追加されると、比較は合法的でした(コンパイラの見込み)。オペレーターのオーバーロードの作成者は、このロジックを処理する責任があります(明らかに、この場合、Microsoftはこれを逃しました)。

構造体の実装(およびそれが何を表すか)によっては、nullとの比較が完全に有効である可能性があるため、これが可能です。

1
Ken Henderson