Operator Overloading with Interface-Based Programming in C#
我在当前项目中使用基于接口的编程,并且在重载运算符(特别是等式和不等式运算符)时遇到了问题。
假设- 我正在使用C 3.0、.NET 3.5和Visual Studio 2008
更新-以下假设是错误的!
- 要求所有比较使用equals而不是operator==不是一个可行的解决方案,尤其是在将类型传递到库(如集合)时。
我担心要求使用equals而不是operator==的原因是,在.NET指南中找不到它声明将使用equals而不是operator==或甚至建议使用equals的地方。但是,在重新阅读重写equals和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.
这个相等的界面
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.
违章
- 任何解决方案都不需要将对象从接口强制转换为具体类型。
问题
- 当运算符==的两边都是接口时,来自基础具体类型的运算符==重载方法签名将不匹配,因此将调用默认的对象运算符==方法。
- 在类上重载运算符时,二进制运算符的至少一个参数必须是包含类型,否则将生成编译器错误(错误bc33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx)
- 不能在接口上指定实现
请参阅下面演示问题的代码和输出。
问题在使用接口基编程时,如何为类提供适当的运算符重载?
工具书类==操作员(C参考)
对于预定义的值类型,如果操作数的值相等,则相等运算符(==)返回"真",否则返回"假"。对于字符串以外的引用类型,==如果其两个操作数引用同一对象,则返回true。对于字符串类型,==比较字符串的值。
也见代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 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."); } } } } |
产量
1 2 | Address operator== overload called Equal with both sides cast. |
简短回答:我认为你的第二个假设可能有缺陷。
答案很长:操作符的过载解析是在编译时执行的,而不是运行时。
除非编译器能明确地知道它要对其应用运算符的对象的类型,否则它不会编译。由于编译器不能确定
为了更清楚地看到这一点,请尝试为
您的挫折可能部分源于这样一个事实:
Requiring all comparisons to use Equals rather than operator== is not a viable solution, especially when passing your types to libraries (such as Collections).
在我看来,这正是你应该做的。
但是
我们遇到了同样的问题,并找到了一个很好的解决方案:重新划分自定义模式。
我们配置了所有用户除了使用自己的模式外,还使用一个通用的全局模式目录,并将其放入SVN中,以便每个人都能对其进行版本控制和更新。
目录中包含了我们系统中已知错误的所有模式:
替换模式是
严重性为"显示为错误"。
同样,我们也有
希望这有帮助。P.S.Global Catalogs是Resharper 6.1(EAP)中的功能,将很快标记为最终产品。
更新:我提交了一个resharper问题,将所有接口"=="标记为一个警告,除非它与空值进行比较。如果你认为这是一个有价值的功能,请投票。
update2:resharper还具有[canNoTapplyEqualityOperator]属性,可以提供帮助。