c# - C# 中基于接口(interface)编程的运算符重载

标签 c# .net operator-overloading equals

背景

我在当前项目中使用基于接口(interface)的编程,在重载运算符(特别是等式和不等式运算符)时遇到了问题。


假设

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

更新 - 以下假设是错误的!

  • 要求所有比较都使用等于而不是运算符== 不是一个可行的解决方案,尤其是在将类型传递给库(例如集合)时。

我担心要求使用 Equals 而不是 operator== 的原因是我在 .NET 指南中找不到任何地方声明它会使用 Equals 而不是 operator== 甚至建议它。然而,重读后Guidelines for Overriding Equals and Operator==我发现了这个:

By default, the operator == tests for reference equality by determining whether two references indicate the same object. Therefore, reference types do not have to implement operator == in order to gain this functionality. When a type is immutable, that is, the data that is contained in the instance cannot be changed, overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they can be considered the same as long as they have the same value. It is not a good idea to override operator == in non-immutable types.

还有这个Equatable Interface

The IEquatable interface is used by generic collection objects such as Dictionary, List, and LinkedList when testing for equality in such methods as Contains, IndexOf, LastIndexOf, and Remove. It should be implemented for any object that might be stored in a generic collection.


约束

  • 任何解决方案都不得要求将对象从其接口(interface)转换为具体类型。

问题

  • 当 operator== 的两边都是接口(interface)时,底层具体类型的 operator== 重载方法签名将不匹配,因此将调用默认的 Object operator== 方法。
  • 在类上重载运算符时,二元运算符的至少一个参数必须是包含类型,否则会产生编译错误(错误 BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx )
  • 不可能在接口(interface)上指定实现

请参阅下面演示问题的代码和输出。


问题

在使用基于接口(interface)的编程时,如何为您的类提供适当的运算符重载?


引用资料

== Operator (C# Reference)

对于预定义的值类型,相等运算符 (==) 如果其操作数的值相等则返回 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.

最佳答案

简短回答:我认为您的第二个假设可能有缺陷。 Equals() 是检查两个对象语义相等的正确方法,而不是operator ==


长答案:运算符的重载解析在编译时执行,而不是运行时

除非编译器可以明确地知道它正在应用运算符的对象的类型,否则它不会编译。由于编译器无法确定 IAddress 是否会覆盖 == 定义的内容,因此它会回退到默认的 operator == System.Object 的实现。

要更清楚地看到这一点,请尝试为 Address 定义一个 operator + 并添加两个 IAddress 实例。除非您显式转换为 Address,否则它将无法编译。为什么?因为编译器无法判断特定的 IAddress 是一个 Address,并且没有默认的 operator + 实现可以回退到 System.Object


部分原因可能是 Object 实现了一个 operator ==,而一切都是 Object,因此编译器可以为所有类型成功解析像 a == b 这样的操作。当您覆盖 == 时,您希望看到相同的行为但没有,这是因为编译器可以找到的最佳匹配是原始 Object 实现。

Requiring all comparisons to use Equals rather than operator== is not a viable solution, especially when passing your types to libraries (such as Collections).

在我看来,这正是您应该做的。 Equals() 是检查两个对象语义相等的正确方法。有时语义相等只是引用相等,在这种情况下你赢了不需要改变任何东西。在其他情况下,如您的示例所示,当您需要比引用平等更强大的平等契约时,您将覆盖 Equals 。例如,如果两个 Persons 具有相同的社会安全号码,您可能想认为它们相等,或者如果两个 Vehicles 具有相同的 VIN,则它们相等。

但是 Equals()operator == 不是一回事。每当您需要覆盖 operator == 时,您应该覆盖 Equals(),但几乎永远不要反过来。 operator == 更像是一种语法上的便利。某些 CLR 语言(例如 Visual Basic.NET)甚至不允许您覆盖相等运算符。

关于c# - C# 中基于接口(interface)编程的运算符重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/728434/

相关文章:

c++ - 运算符重载的矩阵乘法

c# - 如何以编程方式强制中断 VBE 编辑器

c# - 是否有可能为 windows 8 开发一个 .Net 应用程序,它也适用于 windows 7 和 Vista

c# - 如何在一个类中获得一个长时间运行的方法,以将其进度报告给 C# 中的调用类?

c# - VS 2012 没有列出 Microsoft.Office.Interop 程序集,但它可以在 GAC 中找到

c++ - 运算符重载的工作原理

c++ - 运算符重载: “operator+”必须采用零或一个参数

c# - 进度条和计时器

c# - 在 ASP.NET MVC 4 中提供打印功能

c# - 使用 WCF 服务拒绝连接