30

たとえば、私の教授は次のように Equals を実装しました。

public class Person {
    private string dni;

    // ...

    public override bool Equals(object o) {
        if (o == null)
            return (this == null);
        else {
            return ((o is Person) && (this.dni == (o as Person).dni));
        }
    }
}

私はC#の経験はありませんが、私が知る限りthis、メンバー関数内でnullにすることはできません(少なくともこれは私が知っている言語であるC ++とJavaに当てはまります)ので、ifは奇妙に思えます。

私は正しいですか、this == nullそれともテストが必要になるか分からないc#のコンポーネントはありますか?

4

4 に答える 4

58

私はC#の経験はありませんが、私が知る限り、メンバー関数内でこれをnullにすることはできません(少なくともこれは、私が知っている言語であるC++とJavaに当てはまります)

あなたの発言が間違っていることに注意することから始めましょう。

C++ では、null レシーバーでメソッドをディスパッチすることは未定義の動作であり、未定義の動作は何でも起こり得ることを意味します。「何でも」には、何も問題がなかったかのように通過NULLthis、継続するプログラムが含まれます。もちろんthis、C++ で が nullかどうかをチェックするのはややばかげています。これは、プログラムの動作が定義されていないため、プログラムが何を行っているかを既に知らない場合にのみチェックが true になる可能性があるためです。

thisJavaでnullにできるかどうかはわかりません。

ここで、C# に関する質問に答えます。過負荷ではないと仮定しましょう==。この点については後で説明します。

メソッドは C# で記述されています。null レシーバーを使用して C# プログラムから呼び出されたとします。C# コンパイラは、レシーバーが null になる可能性があるかどうかを評価します。null の可能性がある場合は、メソッドを呼び出す前に null チェックを行うコードを生成するようにします。したがって、このチェックはそのシナリオでは無意味です。もちろん、これは 99.9999% の可能性があるシナリオです。

mike z's answer のように、リフレクションを介して呼び出されたとします。その場合、呼び出しを実行しているのは C# 言語ではありません。むしろ、誰かが反射を意図的に悪用しています。

別の言語から呼び出されたとします。仮想メソッドがあります。仮想ディスパッチを使用してこの他の言語から呼び出された場合、null チェックを実行する必要があります。なぜなら、仮想スロットにあるものを他にどのように知ることができるでしょうか? そのシナリオでは、null にすることはできません。

しかし、非仮想ディスパッチを使用して別の言語から呼び出されたとします。その場合、他の言語は null をチェックする C# 機能を実装する必要はありません。それを呼び出して null を渡すだけです。

したがって、C# にはいくつかの方法がありますthisnull、それらはすべて主流から外れています。したがって、教授のようにコードを書く人は非常にまれです。C# プログラマーは慣用的に、そうでthisはないnullと想定し、チェックすることはありません。

邪魔にならないようになったので、そのコードをもう少し批判しましょう。

public override bool Equals(object o) {
    if (o == null)
        return (this == null);
    else {
        return ((o is Person) && (this.dni == (o as Person).dni));
    }
}

まず、明らかなバグがあります。これは null の可能性があると思いthisます。よし、それで実行しましょう。 null参照例外のスローを停止するものは何ですかthis.dni??? それが null になる可能性があると想定する場合はthis、少なくとも一貫してそうしてください! (Coverity では、この種の状況を「前方ヌル欠陥」と呼んでいます。)

次:おそらく参照の等価性を意味するために、オーバーライドEqualsしてから inside を使用しています。この方法は狂気です!これで、 trueでも falseにもなり得る状況が発生しました。これは恐ろしいです。そこに行かないでください。オーバーライドする場合は、同時にオーバーロードし、その間に実装します。 ==x.Equals(y)x==yEquals==IEquatable<T>

(ここで、狂気はどちらの方向にもあるという合理的な議論があります。が値セマンティクス==と一致している場合、 は とは異なる可能性があります。これも奇妙に思えます。ここでのポイントは、C# では等価性がかなり混乱しているということです。)Equalspersonx == persony(object)personx == (object)persony

さらに、==後でオーバーライドされた場合はどうなりますか? コードの作成者が明らかに参照比較を行いたい場合、Equalsオーバーライドされた演算子を呼び出すようになりました。==これはバグのレシピです。

私の推奨事項は、(1) 正しいことを行う静的メソッドを 1 つ作成すること、および (2)ReferenceEquals等価性の意味について混乱が生じる可能性がある場合は常に使用することです。

private static bool Equals(Person x, Person y)
{
    if (ReferenceEquals(x, y))
        return true;
    else if (ReferenceEquals(x, null))
        return false;
    else if (ReferenceEquals(y, null))
        return false;
    else 
        return x.dni == y.dni;
}

それはすべてのケースをうまくカバーします。参照等価セマンティクスが意図されている場合、読者にとって非常に明確であることに注意してください。また、このコードにより、デバッグの目的で、それぞれの可能性にブレークポイントを簡単に設定できることに注意してください。そして最後に、できるだけ安いものを早い段階で取っていることに注意してください。オブジェクトが等しい参照である場合、コストがかかる可能性のあるフィールドの比較を行う必要はありません!

他の方法は簡単です。

public static bool operator ==(Person x, Person y) 
{
  return Equals(x, y);
}
public static bool operator !=(Person x, Person y) 
{
  return !Equals(x, y);
}
public override bool Equals(object y)
{
  return Equals(this, y as Person);
}
public bool Equals(Person y)
{
  return Equals(this, y);
}

あなたの教授の方法よりも、私の方法の方がはるかにエレガントで明快であることに注目してください。また、私のやり方ではnull と直接this比較することなくnull を処理していることに注意してください。this

繰り返しますが、これはすべて、値と参照の両方の等価性が可能であり、等価性を実装する4 つの ( ==!=object.Equals(object)および) 方法があるという妥協点に到達したことを示しています。IEquatable<T>.Equals(T)thisnull

このテーマに興味がある方は、今週のブログで少し難しい問題について説明します。それは、不等式を含む一般的な比較を実装する方法です。

http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/

コメントは、C# がどのように等価性を処理するかについての批評として特に興味深いものです。

最後に: override を忘れないでくださいGetHashCode正しく行うようにしてください。

于 2013-10-10T15:59:18.210 に答える
13

はい、場合によっては。最も一般的なケースは、リフレクションによって作成されたデリゲートを使用してインスタンス メソッドを呼び出し、null レシーバーを渡す場合です。これにより、「True」が出力されます。

public class Test
{
    public void Method()
    {
        Console.WriteLine(this == null);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        object t = new Test();
        var methodInfo = t.GetType().GetMethod("Method");
        var a = (Action)Delegate.CreateDelegate(typeof(Action), null, methodInfo);
        a();
    }
}

正直なところ、実稼働コードで実際thisに null をチェックしている人を見たことがありません。

于 2013-10-10T15:46:25.900 に答える
2

1 つの方法thisは、オペレーター== nullを過負荷にして黒魔術を練習した場合です。==たとえば、誰かが null または空の文字列を持つことdniは null の人に等しいと判断した場合:

public static bool operator ==(Person a, Person b)
{
    if(String.IsNullOrEmpty(a.dni) && (object)b == null)
        return true;

    if(String.IsNullOrEmpty(b.dni) && (object)a == null)
        return true;

    return Object.ReferenceEquals(a, b);
}

これはおそらく本当に悪い考えであることに注意してください。

于 2013-10-10T15:45:00.793 に答える
2

はい、可能です。C# 言語は、クラス オブジェクトへの null 参照でメソッドを呼び出すことができないという優れた保証を提供し、呼び出しサイトで null チェックを行うコードを生成します。これは確かに多くの頭を悩ませることを回避します。メソッド内の NullReferenceException は、そのチェックなしでデバッグするのがかなり難しい場合があります。

ただし、このチェックは C# に固有のものであり、すべての .NET 言語で実装されているわけではありません。たとえば、C++/CLI 言語はそうではありません。C++ CLR コンソール モード プロジェクトと C# クラス ライブラリを使用してソリューションを作成することで、それを実際に確認できます。CLR プロジェクトの参照を C# プロジェクトに追加します。

C# コード:

using System;

namespace ClassLibrary1 {
    public class Class1 {
        public void NullTest() {
            if (this == null) Console.WriteLine("It's null");
        }
    }
}

C++/CLI コード:

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args) {
    ClassLibrary1::Class1^ obj = nullptr;
    obj->NullTest();
    Console::ReadLine();
    return 0;
}

出力:

ヌルです

これは、未定義の動作でも違法でもありません。CLI 仕様では禁止されていません。メソッドがインスタンス メンバーにアクセスしない限り、何も問題はありません。CLR もこれを処理し、null ではないポインターに対しても NullReferenceException が生成されます。NullTest() がクラスの最初のフィールドではないフィールドにアクセスする場合と同様です。

于 2013-10-12T04:29:13.913 に答える