1. ホーム
  2. c#

インターフェイスベースドプログラミングによる演算子のオーバーロード in C#

2023-10-16 03:43:14

質問

背景

私は現在のプロジェクトでインターフェイス ベースのプログラミングを使用していますが、演算子 (特に Equality および Inequality 演算子) をオーバーロードするときに問題に遭遇しました。


前提条件

  • C# 3.0、.NET 3.5、および Visual Studio 2008 を使用しています。

UPDATE - 次の仮定は誤りでした!

  • operator== ではなく Equals を使用するようすべての比較を要求することは、特にライブラリ (Collections など) に型を渡す場合、実行可能な解決策ではありません。

私が operator== ではなく Equals を使用することを要求することについて懸念していた理由は、.NET ガイドラインのどこにも operator== ではなく Equals を使用することを述べている箇所やそれを示唆している箇所さえ見つけることができなかったからです。しかし、再読したところ Equals および Operator== をオーバーライドするためのガイドライン を読み直したところ、このようになりました。

デフォルトでは、演算子==は2つの参照が同じオブジェクトを示すかどうかを判断することによって、参照の等質性をテストします。したがって、参照型はこの機能を得るために演算子==を実装する必要はありません。型が不変である場合、つまりインスタンスに含まれるデータを変更できない場合、不変オブジェクトとして同じ値を持っている限り同じとみなすことができるため、参照の等質性の代わりに値の等質性を比較するために演算子==をオーバーロードすると便利なことがある。不変でない型に演算子==をオーバーライドするのは得策ではありません。

と、この 等価インターフェイス

IEquatable インターフェースは、Contains、IndexOf、LastIndexOf、Remove などのメソッドで等価性をテストするときに、Dictionary、List、LinkedList などの汎用コレクションオブジェクトで使用されます。これは、一般的なコレクションに格納される可能性のあるあらゆるオブジェクトのために実装されるべきです。


制約事項

  • どのような解決策も、オブジェクトをそのインターフェイスからその具象型にキャストすることを必要としません。

問題点

  • operator== の両側がインターフェースである場合、基礎となる具象型からの operator== オーバーロード メソッドのシグネチャは一致せず、デフォルトの Object operator== メソッドが呼び出されます。
  • クラス上で演算子をオーバーロードする場合、二項演算子のパラメータの少なくとも 1 つは包含型でなければならず、そうでない場合はコンパイラエラーが発生します (エラー BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx )
  • インターフェースに実装を指定することはできません。

以下のコードと出力で、この問題のデモをご覧ください。


質問

インターフェースベースのプログラミングを使用する場合、クラスの適切な演算子オーバーロードをどのように提供するのでしょうか?


参考文献

== 演算子 (C#のリファレンス)

定義済みの値型に対して、等号演算子(==)はオペランドの値が等しい場合に真を、そうでない場合に偽を返します。文字列以外の参照型では、その2つのオペランドが同じオブジェクトを参照していれば真を返します。文字列型の場合、==は文字列の値を比較する。


参照


コード

using System;

namespace OperatorOverloadsWithInterfaces
{
    public interface IAddress : IEquatable<IAddress>
    {
        string StreetName { get; set; }
        string City { get; set; }
        string State { get; set; }
    }

    public class Address : IAddress
    {
        private string _streetName;
        private string _city;
        private string _state;

        public Address(string city, string state, string streetName)
        {
            City = city;
            State = state;
            StreetName = streetName;
        }

        #region IAddress Members

        public virtual string StreetName
        {
            get { return _streetName; }
            set { _streetName = value; }
        }

        public virtual string City
        {
            get { return _city; }
            set { _city = value; }
        }

        public virtual string State
        {
            get { return _state; }
            set { _state = value; }
        }

        public static bool operator ==(Address lhs, Address rhs)
        {
            Console.WriteLine("Address operator== overload called.");
            // If both sides of the argument are the same instance or null, they are equal
            if (Object.ReferenceEquals(lhs, rhs))
            {
                return true;
            }

            return lhs.Equals(rhs);
        }

        public static bool operator !=(Address lhs, Address rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Address address = obj as Address;
            return this.Equals(address);
        }

        public override int GetHashCode()
        {
            string composite = StreetName + City + State;
            return composite.GetHashCode();
        }

        #endregion

        #region IEquatable<IAddress> Members

        public virtual bool Equals(IAddress other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return ((this.City == other.City)
                && (this.State == other.State)
                && (this.StreetName == other.StreetName));
        }

        #endregion
    }

    public class Program
    {
        static void Main(string[] args)
        {
            IAddress address1 = new Address("seattle", "washington", "Awesome St");
            IAddress address2 = new Address("seattle", "washington", "Awesome St");

            functionThatComparesAddresses(address1, address2);

            Console.Read();
        }

        public static void functionThatComparesAddresses(IAddress address1, IAddress address2)
        {
            if (address1 == address2)
            {
                Console.WriteLine("Equal with the interfaces.");
            }

            if ((Address)address1 == address2)
            {
                Console.WriteLine("Equal with Left-hand side cast.");
            }

            if (address1 == (Address)address2)
            {
                Console.WriteLine("Equal with Right-hand side cast.");
            }

            if ((Address)address1 == (Address)address2)
            {
                Console.WriteLine("Equal with both sides cast.");
            }
        }
    }
}


出力

Address operator== overload called
Equal with both sides cast.

どのように解決するのですか?

簡単な答えです。2 番目の仮定に欠陥がある可能性があると思います。 Equals() をチェックするのは正しい方法です。 意味的な等質性 ではなく、2つのオブジェクトの operator == .


長い回答です。演算子のオーバーロードの解決は 実行時ではなく、コンパイル時に実行されます。 .

コンパイラは演算子を適用するオブジェクトの型を決定的に知ることができない限り、コンパイルすることはできません。コンパイラは IAddress をオーバーライドするものであることを確認できないので、コンパイラは == が定義されている場合、デフォルトの operator == の実装は System.Object .

これをより明確に見るために operator + に対して Address を追加し、2つの IAddress のインスタンスを作成します。 に明示的にキャストしない限り Address にキャストしない限り、コンパイルに失敗します。なぜか?コンパイラは、ある特定の IAddressAddress であり、デフォルトの operator + の実装にフォールバックすることはできません。 System.Object .


あなたのフラストレーションの一部は、おそらく Objectoperator == を実装しており、すべてが Object であるため、コンパイラは以下のような操作を正常に解決することができます。 a == b のような操作を全ての型に対して正常に解決できるようになります。オーバーライドすると == をオーバーロードしたとき、同じ動作が見られると期待しましたが、見られませんでした。これは、コンパイラが見つけることができる最も良い一致は、オリジナルの Object の実装と最もよく一致するからです。

すべての比較で operator== ではなく Equals を使用するよう要求することは、特にライブラリ (Collections など) に型を渡す場合、実行可能な解決策ではありません。

私の見解では、これはまさにあなたが行うべきことです。 Equals() をチェックするのは正しい方法です。 意味的な等質性 は、2つのオブジェクトの 時には、意味的な等価性は単なる参照等価性であり、その場合は何も変更する必要はありません。他のケースでは、あなたの例のように、オーバーライドする Equals を上書きします。例えば、2 つの Persons が同じ社会保障番号を持っていれば等しいと考えたり、 あるいは二つの Vehicles は同じ車体番号であれば等しくなります。

しかし Equals()operator == は同じものではありません。オーバーライドする必要があるときはいつでも operator == を上書きする必要があります。 Equals() を上書きすべきですが、その逆はほとんどありません。 operator == はどちらかというと構文的な便宜のためです。CLR 言語 (Visual Basic.NET など) の中には、等号演算子をオーバーライドすることさえ許可していないものがあります。