Domanda

Sfondo

Sto utilizzando la programmazione basata sull'interfaccia su un progetto attuale e ho riscontrato un problema durante il sovraccarico degli operatori (in particolare gli operatori di uguaglianza e disuguaglianza).


Ipotesi

  • Utilizzo C# 3.0, .NET 3.5 e Visual Studio 2008

AGGIORNAMENTO - La seguente ipotesi era falsa!

  • Richiedere che tutti i confronti utilizzino Equals anziché operator== non è una soluzione praticabile, soprattutto quando si passano i tipi alle librerie (come Collections).

Il motivo per cui ero preoccupato di richiedere l'utilizzo di Equals anziché operator== è che non sono riuscito a trovare da nessuna parte nelle linee guida .NET che affermassero che avrebbe utilizzato Equals anziché operator== o addirittura suggerirlo.Tuttavia, dopo aver riletto Linee guida per sovrascrivere l'operatore uguale e uguale== Ho trovato questo:

Per impostazione predefinita, l'operatore == verifica l'uguaglianza dei riferimenti determinando se due riferimenti indicano lo stesso oggetto.Pertanto, i tipi di riferimento non devono implementare operator == per ottenere questa funzionalità.Quando un tipo è immutabile, ovvero i dati contenuti nell'istanza non possono essere modificati, l'overload dell'operatore == per confrontare l'uguaglianza dei valori invece dell'uguaglianza dei riferimenti può essere utile perché, in quanto oggetti immutabili, possono essere considerati uguali a long poiché hanno lo stesso valore.Non è una buona idea sovrascrivere l'operatore == nei tipi non immutabili.

e questo Interfaccia equabile

L'interfaccia IEquatable viene utilizzata da oggetti di raccolta generici come Dictionary, List e LinkedList durante il test dell'uguaglianza in metodi come Contiene, IndexOf, LastIndexOf e Remove.Dovrebbe essere implementato per qualsiasi oggetto che potrebbe essere archiviato in una raccolta generica.


Vincoli

  • Qualsiasi soluzione non deve richiedere il trasferimento degli oggetti dalle loro interfacce ai loro tipi concreti.

Problema

  • Ogni volta che entrambi i lati di operator== sono un'interfaccia, nessuna firma del metodo di sovraccarico operator== dai tipi concreti sottostanti corrisponderà e quindi verrà chiamato il metodo Object operator== predefinito.
  • Quando si esegue l'overload di un operatore su una classe, almeno uno dei parametri dell'operatore binario deve essere del tipo contenitore, altrimenti viene generato un errore del compilatore (Errore BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx)
  • Non è possibile specificare l'implementazione su un'interfaccia

Vedere il codice e l'output di seguito che dimostrano il problema.


Domanda

Come si fornisce il corretto sovraccarico degli operatori per le classi quando si utilizza la programmazione basata sull'interfaccia?


Riferimenti

== Operatore (riferimento C#)

Per i tipi di valore predefiniti, l'operatore di uguaglianza (==) restituisce true se i valori dei suoi operandi sono uguali, false altrimenti.Per tipi di riferimento diversi da string, == restituisce true se i suoi due operandi si riferiscono allo stesso oggetto.Per il tipo stringa, == confronta i valori delle stringhe.


Guarda anche


Codice

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.");
            }
        }
    }
}

Produzione

Address operator== overload called
Equal with both sides cast.
È stato utile?

Soluzione

Risposta breve:Penso che la tua seconda ipotesi potrebbe essere errata. Equals() è il modo giusto per verificare uguaglianza semantica di due oggetti, no operator ==.


Risposta lunga:La risoluzione del sovraccarico per gli operatori è eseguito in fase di compilazione, non in fase di esecuzione.

A meno che il compilatore non possa conoscere con certezza i tipi degli oggetti a cui sta applicando un operatore, non compilerà.Poiché il compilatore non può essere sicuro che an IAddress sarà qualcosa per cui ha un override == definito, ritorna al valore predefinito operator == implementazione di System.Object.

Per vederlo più chiaramente, prova a definire an operator + per Address e aggiungendone due IAddress istanze. A meno che tu non trasmetta esplicitamente a Address, la compilazione non riuscirà.Perché?Perché il compilatore non può dirlo in modo particolare IAddress è un Address, e non esiste alcuna impostazione predefinita operator + implementazione a cui ricorrere System.Object.


Parte della tua frustrazione probabilmente deriva da questo Object implementa un operator ==, e tutto è un Object, in modo che il compilatore possa risolvere con successo operazioni come a == b per tutti i tipi.Quando hai eseguito l'override ==, ti aspettavi di vedere lo stesso comportamento ma non è stato così, e questo perché la migliore corrispondenza che il compilatore può trovare è l'originale Object implementazione.

Richiedere che tutti i confronti utilizzino Equals anziché operator== non è una soluzione praticabile, soprattutto quando si passano i tipi alle librerie (come Collections).

A mio avviso, questo è esattamente ciò che dovresti fare. Equals() è il modo giusto per verificare uguaglianza semantica di due oggetti. A volte l'uguaglianza semantica è solo l'uguaglianza dei riferimenti, nel qual caso non sarà necessario modificare nulla.In altri casi, come nel tuo esempio, eseguirai l'override Equals quando è necessario un contratto di uguaglianza più forte dell’uguaglianza di riferimento.Ad esempio, potresti considerarne due Persons uguali se hanno lo stesso numero di previdenza sociale, o due Vehicles uguali se hanno lo stesso VIN.

Ma Equals() E operator == non sono la stessa cosa.Ogni volta che è necessario eseguire l'override operator ==, dovresti eseguire l'override Equals(), ma quasi mai il contrario. operator == è più una comodità sintattica.Alcuni linguaggi CLR (es.Visual Basic.NET) non consentono nemmeno di sovrascrivere l'operatore di uguaglianza.

Altri suggerimenti

Abbiamo riscontrato lo stesso problema e abbiamo trovato un'ottima soluzione:Modelli personalizzati più nitidi.

Abbiamo configurato TUTTI i nostri utenti per utilizzare un catalogo di pattern globale comune oltre al proprio e lo abbiamo inserito in SVN in modo che possa essere aggiornato e aggiornato per tutti.

Il catalogo includeva tutti i modelli noti per essere errati nel nostro sistema:

$i1$ == $i2$ (dove sono i1 e i2 espressioni del nostro tipo di interfaccia, o derivati.

il modello di sostituzione è

$i1$.Equals($i2$)

e la gravità è "Mostra come errore".

Allo stesso modo abbiamo $i1$ != $i2$

Spero che questo ti aiuti.PSI cataloghi globali sono la funzionalità di Resharper 6.1 (EAP) e verranno contrassegnati come definitivi molto presto.

Aggiornamento:Ho presentato una Problema di riaffilatura per contrassegnare tutte le interfacce '==' con un avviso a meno che non si confronti con null.Per favore vota se pensi che sia una caratteristica degna di nota.

Aggiornamento2:Resharper dispone anche dell'attributo [CannotApplyEqualityOperator] che può essere d'aiuto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top