Фон
Я использую интерфейсное программирование в текущем проекте и столкнулся с проблемой при перегрузке операторов (в частности, операторов Equality and Inequality).
Предположения
- Я использую С# 3.0,.NET 3.5 и Visual Studio 2008
ОБНОВЛЕНИЕ - следующее предположение было ложным!
- Требование ко всем сравнениям использовать Equals, а не оператор ==, не является жизнеспособным решением, особенно при передаче типов в библиотеки (например, Коллекции).
Причина, по которой я был обеспокоен необходимостью использования Equals, а не оператора ==, заключается в том, что я не мог найти нигде в руководящих принципах .NET, что он заявил, что будет использовать Equals, а не оператор ==, или даже предложить его. Однако после повторного чтения Рекомендации по переопределению равных и операторов == я нашел это:
По умолчанию оператор == проверяет ссылочное равенство, определяя, указывают ли две ссылки на один и тот же объект. Поэтому ссылочные типы не должны реализовывать оператор ==, чтобы получить эту функциональность. Если тип неизменен, то есть данные, которые содержатся в экземпляре, не могут быть изменены, перегрузка оператора == для сравнения равенства значений вместо ссылочного равенства может быть полезна, поскольку в качестве неизменяемых объектов их можно считать такими же длинными поскольку они имеют одинаковую ценность. Не рекомендуется переопределять оператор == в неизменяемых типах.
Интерфейс IEquatable используется универсальными объектами коллекции, такими как Dictionary, List и LinkedList при тестировании на равенство в таких методах, как Contains, IndexOf, LastIndexOf и Remove. Он должен быть реализован для любого объекта, который может быть сохранен в общей коллекции.
контрсилы
- Любое решение не должно требовать литье объектов с их интерфейсов на их конкретные типы.
Проблема
- Когда обе стороны оператора == являются интерфейсом, никакая сигнатура метода перегрузки оператора == из базовых конкретных типов не будет соответствовать, и поэтому будет вызываться метод объекта Object == по умолчанию.
- При перегрузке оператора в классе по крайней мере один из параметров двоичного оператора должен быть содержащим типом, в противном случае генерируется ошибка компилятора (ошибка BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx)
- Невозможно указать реализацию на интерфейсе
См. раздел "Код и вывод" ниже, демонстрирующий проблему.
Вопрос
Как вы обеспечиваете надлежащую перегрузку оператора для своих классов при использовании программирования на основе интерфейса?
Ссылки
Для предопределенных типов значений оператор равенства (==) возвращает true, если значения его операндов равны, в противном случае - false. Для ссылочных типов, отличных от строки, == возвращает 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.