出身背景

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


假设

  • 我用的是C#3.0.NET 3.5和Visual Studio 2008

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

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

我之所以担心需要使用等于而不是运算符==是因为我在.NET指南,声明将使用等于而不是运算符==或甚至建议使用它.然而,在重读Guidelines for Overriding Equals and Operator==之后,我发现:

默认情况下,运算符==通过确定两个引用是否指示同一对象来测试引用相等性.因此,引用类型不必实现运算符==即可获得此功能.当类型是不可变的,即实例中包含的数据不能更改时,重载运算符==以比较值相等而不是引用相等可能很有用,因为作为不可变对象,只要它们具有相同的值,就可以认为它们是相同的.在非不可变类型中重写运算符==不是一个好主意.

而这Equatable Interface

测试CONTAINS、IndexOf、LastIndexOf和Remove等方法中的等价性时,Dictionary、List和LinkedList等泛型集合对象使用IEquatable接口.应该为可能存储在泛型集合中的任何对象实现它.


违禁品

  • 任何解决方案都不能要求将对象从其接口转换为其具体类型.

问题

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

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


问题

How do you provide proper operator overloads for your classes when using interface-base programming?


参考文献

== 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 GetHash代码()
        {
            string composite = StreetName + City + State;
            return composite.GetHash代码();
        }

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

推荐答案

简而言之:我认为你的第二个假设可能是有缺陷的.100 is the right way to check for semantic equality of two objects, not 101.


长答案:操作符的重载分辨率是performed at compile time, not run time.

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

To see this more clearly, try defining an 100 for 101 and adding two 102 instances.除非显式转换为Address,否则它将无法编译.为什么?因为编译器无法判断特定的IAddressAddress,而且System.Object中没有默认的operator +实现.


部分原因可能是Object实现了operator ==,而所有东西都是Object,因此编译器可以成功地为所有类型解析a == b之类的操作.当您重写==时,您希望看到相同的行为,但没有看到,这是因为编译器能找到的最佳匹配是最初的Object实现.

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

在我看来,这正是你应该做的.100 is the right way to check for semantic equality of two objects.有时语义平等只是引用平等,在这种情况下,您不需要更改任何内容.在其他情况下,如在您的示例中,当您需要一个比引用平等更强大的平等契约时,您将覆盖Equals.例如,如果他们拥有相同的社会保障号码,或者如果他们拥有相同的VIN,则两个Persons相等,或者两个Vehicles相等.

但是Equals()operator ==不是一回事.当你需要覆盖operator ==时,你应该覆盖Equals(),但绝不能反过来.operator ==在句法上更方便.某些CLR语言(例如Visual Basic.NET)甚至不允许重写相等运算符.

.net相关问答推荐

为什么DotNet新的webapi;命令会为我生成不同的文件夹

如何在dotnet中使用OpenTelemetry Prometheus导出器导出多个版本的度量?

使用 PowerShell 从文件夹中获取文件名的最快\最好的方法是什么?

在.NET C#中截断整个单词的字符串

StreamWriter.Flush() 和 StreamWriter.Close() 有什么区别?

为什么这两个比较有不同的结果?

如何使用 Moq 为不同的参数设置两次方法

如何在没有抽象基类的情况下强制覆盖后代中的方法?

如何解决请确保文件可访问并且它是有效的程序集或 COM 组件?

如何获取 Sql Server 数据库中所有模式的列表

File.ReadAllLines() 和 File.ReadAllText() 有什么区别?

Automapper:使用 ReverseMap() 和 ForMember() 进行双向映射

如何使用c#从excel文件中读取数据

静态代码块

如何从 .net 中的数组类型获取数组项类型

如何正确停止BackgroundWorker

将 StreamReader 返回到开头

合并两个(或更多)PDF

在 .Net 中调用 Web 服务时绕过无效的 SSL 证书错误

当它被抛出和捕获时,不要在那个异常处停止调试器