背景
我正在一个项目中使用基于接口的编程,但在重载运算符(特别是相等和不相等运算符)时遇到了问题。
假设
- 我使用的是C# 3.0、.NET 3.5和Visual Studio 2008
更新 - 以下假设是错误的!
- 要求所有比较都使用Equals而不是operator==不是一种可行的解决方案,特别是当将您的类型传递给库(例如Collections)时。
我之所以担心需要使用Equals而不是operator==,是因为我找不到任何地方在.NET指南中说明它会使用Equals而不是operator==,甚至建议使用它。然而,在重新阅读Guidelines for Overriding Equals and Operator==之后,我发现了这个:
默认情况下,operator==运算符通过确定两个引用是否指示同一对象来测试引用相等性。因此,引用类型不必实现operator==即可获得此功能。当一个类型是不可变的,也就是说,包含在实例中的数据不能被更改时,重载operator==以比较值相等性而不是引用相等性是有用的,因为作为不可变对象,只要它们具有相同的值,就可以被认为是相同的。在非不可变类型中重写operator==不是一个好主意。
当测试包含在Contains、IndexOf、LastIndexOf和Remove等方法中的等式时,通用集合对象(如Dictionary、List和LinkedList)使用IEquatable接口。它应该为任何可能存储在通用集合中的对象实现。
限制
- 任何解决方案都不得要求将对象从其接口转换为其具体类型。
问题
- 每当operator==的两侧都是接口时,底层具体类型的operator==重载方法签名都不会匹配,因此将调用默认的Object operator==方法。
- 在类上重载运算符时,二元运算符的至少一个参数必须是包含类型,否则将生成编译器错误(Error BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx)
- 无法在接口上指定实现
请参见下面的代码和输出,演示了这个问题。
问题
在使用基于接口的编程时,如何为您的类提供适当的运算符重载?
参考资料
对于预定义值类型,等号运算符(==)在其操作数的值相等时返回 true,否则返回 false。对于除 string 以外的引用类型,== 在其两个操作数引用同一对象时返回 true。对于 string 类型,== 比较字符串的值。
另请参阅
代码
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.