C#中基于接口编程的运算符重载
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/728434/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Operator Overloading with Interface-Based Programming in C#
提问by Zach Burlingame
Background
背景
I am using interface-based programming on a current project and have run into a problem when overloading operators (specifically the Equality and Inequality operators).
我在当前项目中使用基于接口的编程,并且在重载运算符(特别是 Equality 和 Inequality 运算符)时遇到了问题。
Assumptions
假设
- I'm using C# 3.0, .NET 3.5 and Visual Studio 2008
- 我使用的是 C# 3.0、.NET 3.5 和 Visual Studio 2008
UPDATE - The Following Assumption was False!
更新 - 以下假设是错误的!
- 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 而不是 operator== 不是一个可行的解决方案,尤其是在将类型传递给库(例如集合)时。
The reason I was concerned about requiring Equals to be used rather than operator== is that I could not find anywhere in the .NET guidelines that it stated it would use Equals rather than operator== or even suggest it. However, after re-reading Guidelines for Overriding Equals and Operator==I have found this:
我担心要求使用 Equals 而不是 operator== 的原因是我在 .NET 指南中找不到任何地方声明它将使用 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.
默认情况下,运算符 == 通过确定两个引用是否指示同一个对象来测试引用相等性。因此,引用类型不必为了获得此功能而实现运算符 ==。当类型是不可变的,即实例中包含的数据不能改变时,重载运算符 == 来比较值相等而不是引用相等会很有用,因为作为不可变对象,它们可以被认为与 long 相同因为它们具有相同的值。在非不可变类型中覆盖运算符 == 不是一个好主意。
and this Equatable Interface
和这个Equatable 接口
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.
当在Contains、IndexOf、LastIndexOf 和Remove 等方法中测试相等性时,通用集合对象(例如Dictionary、List 和LinkedList)使用IEquatable 接口。它应该为可能存储在泛型集合中的任何对象实现。
Contraints
限制条件
- Any solution must not require casting the objects from their interfaces to their concrete types.
- 任何解决方案都不能要求将对象从它们的接口转换为它们的具体类型。
Problem
问题
- When ever both sides of the operator== are an interface, no operator== overload method signature from the underlying concrete types will match and thus the default Object operator== method will be called.
- When overloading an operator on a class, at least one of the parameters of the binary operator must be the containing type, otherwise a compiler error is generated (Error BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx)
- It's not possible to specify implementation on an interface
- 当 operator== 的两边都是接口时,来自底层具体类型的 operator== 重载方法签名不会匹配,因此将调用默认的 Object operator== 方法。
- 在类上重载运算符时,二元运算符的参数中至少有一个必须是包含类型,否则会生成编译器错误(错误 BC33021 http://msdn.microsoft.com/en-us/library/watt39ff .aspx)
- 无法在接口上指定实现
See Code and Output below demonstrating the issue.
请参阅下面的代码和输出来演示该问题。
Question
题
How do you provide proper operator overloads for your classes when using interface-base programming?
在使用基于接口的编程时,您如何为您的类提供适当的运算符重载?
References
参考
For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. For reference types other than string, == returns true if its two operands refer to the same object. For the string type, == compares the values of the strings.
对于预定义的值类型,如果操作数的值相等,则相等运算符 (==) 返回 true,否则返回 false。对于字符串以外的引用类型,如果 == 的两个操作数引用同一个对象,则返回 true。对于字符串类型,== 比较字符串的值。
See Also
也可以看看
Code
代码
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.");
}
}
}
}
Output
输出
Address operator== overload called
Equal with both sides cast.
采纳答案by John Feminella
Short answer: I think your second assumption may be flawed. Equals()
is the right way to check for semantic equalityof two objects, not operator ==
.
简短回答:我认为您的第二个假设可能有缺陷。Equals()
是检查两个对象的语义相等性的正确方法,而不是operator ==
.
Long answer: Overload resolution for operators is performed at compile time, not run time.
长答案:运算符的重载解析是在编译时执行的,而不是运行时。
Unless the compiler can definitively know the types of the objects it's applying an operator to, it won't compile. Since the compiler cannot be sure that an IAddress
is going to be something that has an override for ==
defined, it falls back to the default operator ==
implementation of System.Object
.
除非编译器可以明确地知道它应用运算符的对象的类型,否则它不会编译。由于编译器不能确保的IAddress
将是东西,有一个覆盖的==
定义,它回落到默认operator ==
的实现System.Object
。
To see this more clearly, try defining an operator +
for Address
and adding two IAddress
instances.Unless you explicitly cast to Address
, it will fail to compile. Why? Because the compiler can't tell that a particular IAddress
is an Address
, and there is no default operator +
implementation to fall back to in System.Object
.
要更清楚地看到这一点,请尝试定义一个operator +
forAddress
并添加两个IAddress
实例。除非您显式地强制转换为Address
,否则它将无法编译。为什么?因为编译器无法判断某个特定IAddress
是 an Address
,并且没有默认operator +
实现可以回退到 in System.Object
。
Part of your frustration probably stems from the fact that Object
implements an operator ==
, and everything is an Object
, so the compiler can successfully resolve operations like a == b
for all types. When you overrode ==
, you expected to see the same behavior but didn't, and that's because the best match the compiler can find is the original Object
implementation.
您感到沮丧的部分原因可能是因为Object
实现了 an operator ==
,并且一切都是 an 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 而不是 operator== 不是一个可行的解决方案,尤其是在将类型传递给库(例如集合)时。
In my view, this is precisely what you should be doing. Equals()
is the right way to check for semantic equalityof two objects.Sometimes semantic equality is just reference equality, in which case you won't need to change anything. In other cases, as in your example, you'll override Equals
when you need a stronger equality contract than reference equality. For example, you may want to consider two Persons
equal if they have the same Social Security number, or two Vehicles
equal if they have the same VIN.
在我看来,这正是你应该做的。Equals()
是检查两个对象的语义相等性的正确方法。有时语义相等只是引用相等,在这种情况下,您不需要更改任何内容。在其他情况下,如在您的示例中,Equals
当您需要比引用平等更强大的平等契约时,您将覆盖。例如,Persons
如果他们具有相同的社会安全号码,您可能需要考虑两个相等,或者Vehicles
如果它们具有相同的 VIN ,则两个相等。
But Equals()
and operator ==
are not the same thing. Whenever you need to override operator ==
, you should override Equals()
, but almost never the other way around. operator ==
is more of a syntactical convenience. Some CLR languages (e.g. Visual Basic.NET) don't even permit you to override the equality operator.
但是Equals()
和operator ==
不是一回事。每当您需要覆盖时operator ==
,都应该覆盖Equals()
,但几乎从不反过来。operator ==
更多的是一种语法上的便利。某些 CLR 语言(例如 Visual Basic.NET)甚至不允许您覆盖相等运算符。
回答by Yurik
We ran into the same problem, and found an excellent solution: Resharper custom patterns.
我们遇到了同样的问题,并找到了一个很好的解决方案:Resharper 自定义模式。
We configured ALL of our users to use a common global pattern catalog in addition to their own, and placed it into SVN so that it can be versioned and updated for everyone.
我们将所有用户配置为使用除他们自己之外的通用全局模式目录,并将其放置到 SVN 中,以便每个人都可以对其进行版本控制和更新。
The catalog included all patterns known to be wrong in our system:
该目录包括我们系统中已知的所有错误模式:
$i1$ == $i2$
(where i1 and i2 are expressionsof our interface type, or derived.
$i1$ == $i2$
(其中 i1 和 i2 是我们的接口类型或派生的表达式。
the replace pattern is
替换模式是
$i1$.Equals($i2$)
$i1$.Equals($i2$)
and the severity is "Show as error".
并且严重性是“显示为错误”。
Similarly we have $i1$ != $i2$
同样我们有 $i1$ != $i2$
Hope this helps. P.S. Global catalogs is the feature in Resharper 6.1 (EAP), will be marked as final very soon.
希望这可以帮助。PS 全局目录是 Resharper 6.1 (EAP) 中的功能,很快就会被标记为最终版本。
Update: I filed a Resharper Issueto mark all interface '==' a warning unless it is comparing to null. Please vote if you think it is a worthy feature.
更新:我提交了一个Resharper 问题以将所有接口标记为警告,除非它与 null 进行比较。如果您认为这是一个有价值的功能,请投票。
Update2: Resharper also has [CannotApplyEqualityOperator] attribute that can help.
更新 2:Resharper 也有 [CannotApplyEqualityOperator] 属性可以提供帮助。