operator - c# overload




運算符在C#中使用基於接口的編程進行重載 (2)

背景

我在當前項目中使用基於接口的編程,並在重載運算符(特別是Equality和Inequality運算符)時遇到問題。

假設

  • 我正在使用C#3.0,.NET 3.5和Visual Studio 2008

更新 - 下面的假設是錯誤的!

  • 要求所有比較使用Equals而不是運算符==不是一個可行的解決方案,尤其是在將類型傳遞給庫(如Collections)時。

我擔心需要使用Equals而不是運算符==的原因是,我無法在.NET指南中找到任何地方,它指出它會使用Equals而不是運算符==,甚至不會提示它。 但是,在重新讀取等式和運算符==的指導原則後,我發現:

默認情況下,運算符==通過確定兩個引用是否指示相同的對象來測試引用相等。 因此,引用類型不必實現operator ==以獲得此功能。 當一個類型是不可變的,也就是說,實例中包含的數據是不能改變的,重載operator ==比較值相等性而不是引用相等可能是有用的,因為作為不可變對象,它們可以被認為是相同的long因為它們具有相同的價值。 在非不可變類型中重寫operator ==不是一個好主意。

和這個Equatable接口

當在Contains,IndexOf,LastIndexOf和Remove等方法中測試相等性時,IEquatable接口被泛型集合對象(如Dictionary,List和LinkedList)使用。 它應該用於可能存儲在通用集合中的任何對象。

約束上

  • 任何解決方案都不能要求將對像從其接口轉換為具體類型。

問題

  • 當運算符==的兩端都是接口時,底層具體類型中的operator ==重載方法簽名將不匹配,因此將調用默認的Object operator ==方法。
  • 當在類上重載運算符時,二元運算符的至少一個參數必須是包含類型,否則會生成編譯器錯誤(錯誤BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx
  • 無法在接口上指定實現

請參閱下面的代碼和輸出以說明問題。

在使用基於接口的編程時,如何為您的類提供適當的操作符重載?

參考

==運算符(C#參考)

對於預定義的值類型,如果操作數的值相等,則相等運算符(==)返回true,否則返回false。 對於字符串以外的引用類型,如果其兩個操作數引用同一對象,則==會返回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.

我們遇到了同樣的問題,並找到了一個很好的解決方案:Resharper自定義模式。

我們將所有用戶配置為使用除自己的通用全局模式目錄之外的其他用戶,並將其放入SVN中,以便為每個人進行版本化和更新。

該目錄包括我們的系統中已知錯誤的所有模式:

$i1$ == $i2$ (其中i1和i2是我們的接口類型或派生的表達式

替換模式是

$i1$.Equals($i2$)

嚴重程度是“顯示為錯誤”。

同樣,我們有$i1$ != $i2$

希望這可以幫助。 PS Global目錄是Resharper 6.1(EAP)中的功能,將很快被標記為最終。

更新 :我提交了一個Resharper問題來標記所有接口'=='一個警告,除非它與null比較。 如果您認為這是一項有價值的功能,請投票。

Update2 :Resharper還具有可幫助的[CannotApplyEqualityOperator]屬性。


簡答:我認為你的第二個假設可能有缺陷。 Equals()是檢查兩個對象的語義相同性的正確方法,而不是operator ==

長答案:運算符的重載解析在編譯時執行,而不是運行時執行

除非編譯器能夠明確地知道應用運算符的對象的類型,否則編譯不會。 由於編譯器無法確定IAddress是否會覆蓋==定義的內容,因此它將回退到System.Object的默認operator ==實現。

要更清楚地了解這一點,請嘗試為Address定義一個operator +並添加兩個IAddress實例。 除非您明確轉換為Address ,否則將無法編譯。 為什麼? 由於編譯器無法確定某個特定的IAddress是否為Address ,並且沒有默認的operator +實現返回到System.Object

部分挫折可能源於Object實現了一個operator == ,而且一切都是Object ,所以編譯器可以成功地解析所有類型的操作,如a == b 。 當你重載== ,你期望看到相同的行為,但沒有,這是因為編譯器的最佳匹配可以找到原始的Object實現。

要求所有比較使用Equals而不是運算符==不是一個可行的解決方案,尤其是在將類型傳遞給庫(如Collections)時。

在我看來,這正是你應該做的。 Equals()是檢查兩個對象的語義相等性的正確方法。 有時候,語義平等只是參考平等,在這種情況下,你不需要改變任何東西。 在其他情況下,如您的示例中所示,如果需要比參考等式更強的平等協定, Equals覆蓋Equals 。 例如,如果兩個Persons擁有相同的社會安全號碼,您可能需要考慮兩個Persons平等,或者如果他們具有相同的VIN,則兩個Persons平等。

Equals()operator ==不是一回事。 無論何時您需要重寫operator == ,您都應該重寫Equals() ,但幾乎從不相反。 operator ==更具語法便利性。 一些CLR語言(例如Visual Basic.NET)甚至不允許您覆蓋相等運算符。







equals