2

システムを通過するさまざまな文字列 ID 用に、厳密に型指定された不変のラッパー クラスを作成しました。

抽象 BaseId クラス:

(簡潔にするために一部のエラーチェックとフォーマットを省略しています...)

public abstract class BaseId
{
    // Gets the type name of the derived (concrete) class
    protected abstract string TypeName { get; }

    protected internal string Id { get; private set; }

    protected BaseId(string id) { Id = id; }

    // Called by T.Equals(T) where T is a derived type
    protected bool Equals(BaseId other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;
        return String.Equals(Id, other.Id);
    }

    // warning CS0660 (see comment #1 below)
    //public override bool Equals(object obj) { return base.Equals(obj); }

    public override int GetHashCode()
    {
        return TypeName.GetHashCode() * 17 + Id.GetHashCode();
    }

    public override string ToString()
    {
        return TypeName + ":" + Id;
    }

    // All T1 == T2 comparisons come here (where T1 and T2 are one
    // or more derived types)
    public static bool operator ==(BaseId left, BaseId right)
    {
        // Eventually calls left.Equals(object right), which is
        // overridden in the derived class
        return Equals(left, right);
    }

    public static bool operator !=(BaseId left, BaseId right)
    {
        // Eventually calls left.Equals(object right), which is
        // overridden in the derived class
        return !Equals(left, right);
    }
}

私の目標は、派生クラスが小さくなり、大部分/全体が定型コードで構成されるように、実装の多くを基本クラスに保持することでした。

具体的な DerivedId クラスの例:

この派生型は、独自の追加の状態を定義しないことに注意してください。その目的は、強い型を作成することだけです。

public sealed class DerivedId : BaseId, IEquatable<DerivedId>
{
    protected override string TypeName { get { return "DerivedId"; } }

    public DerivedId(string id) : base(id) {}

    public bool Equals(DerivedId other)
    {
        // Method signature ensures same (or derived) types, so
        // defer to BaseId.Equals(object) override
        return base.Equals(other);
    }

    // Override this so that unrelated derived types (e.g. BarId)
    // NEVER match, regardless of underlying Id string value
    public override bool Equals(object obj)
    {
        // Pass obj or null for non-DerivedId types to our
        // Equals(DerivedId) override
        return Equals(obj as DerivedId);
    }

    // warning CS0659 (see comment #2 below)
    //public override int GetHashCode() { return base.GetHashCode(); }
}

各クラスはコンパイラ警告を生成しています:

  1. BaseId で Object.Equals(object o) をオーバーライドしないと、コンパイル警告が生成されます。

    warning CS0660: 'BaseId' defines operator == or operator != but does not override Object.Equals(object o)

    ただし、BaseId.Equals(object o) を実装すると、Object.Equals(object o) で基本クラスの実装を呼び出すだけになります。とにかく、これがどのように呼び出されるかはわかりません。派生クラスでは常にオーバーライドされ、そこでの実装はこの実装を呼び出しません。

  2. DerivedId で BaseId.GetHashCode() をオーバーライドしないと、コンパイル警告が生成されます。

    warning CS0659: 'DerivedId' overrides Object.Equals(object o) but does not override Object.GetHashCode()

    この派生クラスには追加の状態がないため、BaseId.GetHashCode で基本クラスの実装を呼び出すことを除いて、DerivedId.GetHashCode() の実装で行うことはありません。 ().

コンパイラの警告を抑制するか、メソッドを実装して基本クラスの実装を呼び出させることができますが、何かが欠けていないことを確認したいと考えています。

私がこれを行った方法に奇妙なことがありますか、それとも正しいコードの警告を抑制するためにこれを行う必要があることの 1 つにすぎませんか?

4

3 に答える 3

3

これらがエラーではなく警告である理由は、コードは (おそらく) 引き続き機能しますが、予期しないことを実行する可能性があるためです。警告は、「ちょっと、ここで何か悪いことをしている可能性があります。もう一度調べてください」という大きな赤い旗です。

結局のところ、警告はオンになっています。

この特定のケースでは、一部のコードがオブジェクトObject.Equals(object)の 1 つを呼び出すことができる可能性がありますBaseId。たとえば、誰かが次のように書くことができます。

bool CompareThings(BaseId thing, object other)
{
    return thing.Equals(other);
}

Object.Equals(object)あなたのBaseId型はそれをオーバーライドしないので、コンパイラはへの呼び出しを生成します。このメソッドは、 と同じデフォルトの比較を行いますObject.ReferenceEquals(object)。つまり、 には 2 つの異なる意味がありEqualsます。比較するオブジェクトが実際に type であることを確認した後、オーバーライドObject.Equals(object)して呼び出す必要があります。Equals(BaseId)BaseId

GetHashCodeオブジェクトが新しいフィールドを定義したり、Equals の意味を変更するようなことをしたりしないため、おそらくオーバーライドする必要はありません。しかし、コンパイラはそれを知りません。確かに、フィールドを追加していないことはわかっていますが、オーバーライドしたことはEquals、同等の意味を変更した可能性があることを意味します。また、等価性の意味を変更した場合は、ハッシュ コードの計算方法を変更した (または変更する必要がある) 可能性が非常に高くなります。

等価性を適切に処理しないことは、新しい型を設計する際の間違いの非常に一般的な原因です。コンパイラがこの分野で過度に慎重であることは良いことです。

于 2013-07-24T00:04:31.220 に答える
1

質問の問題#2への対処:

オーバーライドBaseId.GetHashCode()しないとDerivedId、コンパイル警告が生成されます。

メソッドをコメントアウトして次のコードを実行し、GetHashCode()コメントアウトせずに再び実行すると、 の実装がGetHashCodeset2 つのインスタンスを含むPersonが、 の実装を追加すると、 のGetHashCodeインスタンスsetが 1 つだけ含まれていることがわかります。 /classesGetHashCodeは比較に使用します。


class Program
{
    static void Main(string[] args)
    {
        Person p1 = new Person() { FirstName="Joe", LastName = "Smith"};
        Person p2 = new Person() { FirstName="Joe", LastName ="Smith"};

        ISet<Person> set = new HashSet<Person>();
        set.Add(p1);
        set.Add(p2);
        foreach (var item in set)
        {
            Console.WriteLine(item.FirstName);
        }
    }

}
class Person
{
    public string FirstName { get; set; } 
    public string LastName { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        var that = obj as Person;
        if (that == null) return false;

        return 
               FirstName == that.FirstName &&
               LastName == that.LastName;
    }

    public override int GetHashCode() //run the code with and without this method
    {
        int hashCode = 1938039292;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName);
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
        return hashCode;
    }
}
于 2020-08-15T02:32:59.280 に答える