Фон
Я использую интерфейсное программирование в текущем проекте и столкнулся с проблемой при перегрузке операторов (в частности, операторов 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.
