76

バックグラウンド

現在のプロジェクトでインターフェイス ベースのプログラミングを使用していますが、演算子 (具体的には等値演算子と不等値演算子) をオーバーロードするときに問題が発生しました。


仮定

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

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

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

operator== ではなく Equals を使用する必要があることを懸念していた理由は、.NET ガイドラインのどこにも、operator== ではなく Equals を使用すると述べているか、またはそれを提案することさえなかったからです。ただし、 Equals と Operator== をオーバーライドするためのガイドラインを読み直した後、次のことがわかりました。

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

およびこの等価インターフェイス

IEquatable インターフェイスは、Contains、IndexOf、LastIndexOf、Remove などのメソッドで等しいかどうかをテストするときに、Dictionary、List、LinkedList などの汎用コレクション オブジェクトによって使用されます。ジェネリック コレクションに格納される可能性のあるすべてのオブジェクトに対して実装する必要があります。


制約

  • どのソリューションでも、オブジェクトをインターフェイスから具象型にキャストする必要があってはなりません。

問題

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

問題を示す以下のコードと出力を参照してください。


質問

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


参考文献

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

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


関連項目


コード

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.
4

3 に答える 3

60

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


長い答え:演算子の過負荷解決は、実行時ではなくコンパイル時に実行されます

コンパイラーがオペレーターを適用しているオブジェクトのタイプを明確に認識できない限り、コンパイラーはコンパイルされません。IAddressコンパイラーは、が定義済みのオーバーライドを持つものになることを確認できないため、デフォルトの実装に==フォールバックします。operator ==System.Object

これをより明確に確認するには、operator +forを定義して2つのインスタンスAddressを追加してみてください。IAddress明示的ににキャストしない限りAddress、コンパイルに失敗します。なんで?コンパイラは特定IAddressのものがであると判断できず、にフォールバックするAddressデフォルトの実装がないためです。operator +System.Object


Objectフラストレーションの一部はおそらく、を実装するという事実に起因し、operator ==すべてがであるObjectため、コンパイラはa == bすべてのタイプのように操作を正常に解決できます。を上書き==すると、同じ動作が見られると期待していましたが、そうではありませんでした。これは、コンパイラが見つけることができる最適な一致が元のObject実装であるためです。

特にタイプをライブラリ(コレクションなど)に渡す場合は、operator ==ではなくEqualsを使用するようにすべての比較を要求することは、実行可能なソリューションではありません。

私の見解では、これはまさにあなたがすべきことです。2つのオブジェクトの意味的同等性Equals()をチェックする正しい方法です。セマンティックの同等性が単なる参照の同等性である場合もあります。その場合、何も変更する必要はありません。他の場合、あなたの例のように、Equals参照等式よりも強力な等式コントラクトが必要な場合はオーバーライドします。たとえば、Persons社会保障番号が同じ場合は2つが等しいと見なしVehicles、VINが同じ場合は2つが等しいと見なすことができます。

しかしEquals()operator ==同じことではありません。オーバーライドする必要があるときはいつでも、オーバーライドする必要がありますがoperator ==Equals()その逆はほとんどありません。operator ==構文上の利便性があります。一部のCLR言語(Visual Basic.NETなど)では、等式演算子をオーバーライドすることさえできません。

于 2009-04-08T04:22:08.033 に答える
4

私たちは同じ問題に遭遇し、ReSharper カスタム パターンという優れた解決策を見つけました。

すべてのユーザーが独自のカタログに加えて共通のグローバル パターン カタログを使用するように構成し、それを SVN に配置して、すべてのユーザーがバージョン管理および更新できるようにしました。

カタログには、システムで間違っていることがわかっているすべてのパターンが含まれていました。

$i1$ == $i2$ (ここで、i1 と i2 はインターフェイス タイプの、または派生したものです。

交換パターンは

$i1$.Equals($i2$)

重大度は「エラーとして表示」です。

同様に、$i1$ != $i2$

お役に立てれば。PS グローバル カタログは ReSharper 6.1 (EAP) の機能であり、まもなく最終版としてマークされる予定です。

更新: null と比較されていない限り、すべてのインターフェイス '==' を警告としてマークするためにReSharper の問題を提出しました。価値のある機能だと思う場合は、投票してください。

Update2 : ReSharper には、役立つ [CannotApplyEqualityOperator] 属性もあります。

于 2011-12-16T15:35:49.710 に答える