web-dev-qa-db-fra.com

Surcharge de l'opérateur avec programmation basée sur l'interface en C #

Fond

J'utilise une programmation basée sur l'interface sur un projet en cours et j'ai rencontré un problème lors de la surcharge des opérateurs (spécifiquement des opérateurs d'égalité et d'inégalité).


Hypothèses

  • J'utilise C # 3.0, .NET 3.5 et Visual Studio 2008

Mise à jour - L'hypothèse suivante était fausse!

  • Nécessitant toutes les comparaisons d'utiliser des égaux plutôt que de l'opérateur == n'est pas une solution viable, notamment lors de la transmission de vos types aux bibliothèques (telles que les collections).

La raison pour laquelle je m'inquiéter d'avoir besoin d'équivalence d'être utilisée plutôt que de l'opérateur == est que je ne pouvais trouver nulle part dans les directives .NET que cela a déclaré qu'il utiliserait des égaux plutôt que de l'opérateur = ou même le suggérerait. Cependant, après la ré-lecture lignes directrices pour remplacer les égaux et opérateur == J'ai trouvé ceci:

Par défaut, l'opérateur == Tests pour l'égalité de référence en déterminant si deux références indiquent le même objet. Par conséquent, les types de référence ne doivent pas nécessairement mettre en œuvre l'opérateur == afin de gagner cette fonctionnalité. Lorsqu'un type est immuable, c'est-à-dire que les données contenues dans l'instance ne peuvent pas être modifiées, l'opérateur de surcharge == pour comparer la valeur égalité au lieu d'une égalité de référence peut être utile car, comme des objets immuables, ils peuvent être considérés comme les mêmes comme ils ont la même valeur. Ce n'est pas une bonne idée de remplacer l'opérateur == dans des types non immuables.

et cette interface équatable

L'interface IEQUABLE est utilisée par des objets de collecte génériques tels que le dictionnaire, la liste et la liste liée lors du test d'égalité dans de telles méthodes que contenant, indexof, lastinexof et supprimer. Il devrait être mis en œuvre pour tout objet pouvant être stocké dans une collection générique.


Contraintes

  • Toute solution ne doit pas nécessiter de casser les objets de leurs interfaces à leurs types de béton.

Problème

  • Lorsque jamais les deux côtés de l'opérateur == sont une interface, aucune méthode de surcharge d'opérateur == La signature des types de béton sous-jacents correspondre à ce que la méthode de l'opérateur d'objet par défaut soit appelée.
  • Lorsque vous surchargez un opérateur sur une classe, au moins un des paramètres de l'opérateur binaire doit être le type contenant, sinon une erreur de compilateur est générée (erreur BC33021 http://msdn.microsoft.com/en-us /Library/watt39ff.aspx )
  • Il n'est pas possible de spécifier la mise en œuvre sur une interface

Voir le code et la sortie ci-dessous démontrant le problème.


Question

Comment fournissez-vous des surcharges appropriées de l'opérateur pour vos classes lors de la programmation de base d'interface?


Les références

== opérateur (référence c #

Pour les types de valeur prédéfinis, l'opérateur d'égalité (==) renvoie true si les valeurs de ses opérandes sont égales, fausses sinon. Pour les types de référence autres que la chaîne, == retourne true si ses deux opérandes font référence au même objet. Pour le type de chaîne, == compare les valeurs des chaînes.


Voir également


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

Sortir

Address operator== overload called
Equal with both sides cast.
71
Zach Burlingame

Réponse courte: Je pense que votre deuxième hypothèse peut être défectueuse. Equals() est la bonne façon de vérifier égalité sémantique de deux objets, pas operator ==.


Réponse longue: la résolution de la surcharge des opérateurs est effectuée au moment de la compilation, pas d'heure d'exécution .

À moins que le compilateur ne puisse définir définitivement les types des objets, il applique un opérateur, il ne compilera pas. Étant donné que le compilateur ne peut pas être sûr que IAddress va être quelque chose qui a une substitution de remplacement de == Défini, il redevient à la mise en œuvre par défaut operator == De System.Object.

Pour voir cela plus clairement, essayez de définir un operator + Pour Address et ajout de deux IAddress instances. Sauf si vous n'êtes pas explicitement lancé sur Address, cela ne parviendra pas à compiler. Pourquoi? Étant donné que le compilateur ne peut pas dire qu'un IAddress est un Address, et il n'y a pas de valeur par défaut operator + Implémentation pour revenir à System.Object.


Une partie de votre frustration provient probablement du fait que Object implémente un operator ==, Et tout est un Object, le compilateur peut donc résoudre avec succès les opérations telles que a == b Pour tous les types. Lorsque vous remplacez ==, Vous êtes censé voir le même comportement, mais ce n'est pas le cas, et c'est parce que le meilleur match du compilateur peut trouver est l'original Object implémentation.

Nécessitant toutes les comparaisons d'utiliser des égaux plutôt que de l'opérateur == n'est pas une solution viable, notamment lors de la transmission de vos types aux bibliothèques (telles que les collections).

À mon avis, c'est précisément ce que vous devriez faire. Equals() est la bonne façon de vérifier égalité sémantique de deux objets. Parfois, l'égalité sémantique est juste Égalité de référence, auquel cas vous n'aurez pas besoin de changer quoi que ce soit. Dans d'autres cas, comme dans votre exemple, vous remplacerez Equals lorsque vous avez besoin d'un contrat d'égalité plus fort que l'égalité de référence. Par exemple, vous voudrez peut-être envisager deux Persons égal si elles ont le même numéro de sécurité sociale, ou deux Vehicles égal si elles ont le même VIN.

Mais Equals() et operator == Ne sont pas la même chose. Chaque fois que vous devez remplacer operator ==, Vous devriez remplacer Equals(), mais presque jamais l'inverse. operator == Est plus d'une commodité syntaxique. Certaines langues CLR (E.G. Visual Basic.net) ne vous permettent même pas de remplacer l'opérateur d'égalité.

56
John Feminella

Nous avons rencontré le même problème et avons trouvé une excellente solution: schémas personnalisés de Restomes.

Nous avons configuré tous nos utilisateurs à utiliser un catalogue de motifs global commun en plus de leur propre et la placé dans SVN afin qu'il puisse être versé et mis à jour pour tout le monde.

Le catalogue comprenait tous les modèles connus pour être faux dans notre système:

$i1$ == $i2$ (Où I1 et I2 sont expressions de notre type d'interface ou dérivé.

le motif de remplacement est

$i1$.Equals($i2$)

et la gravité est "montrant comme une erreur".

De la même manière, nous avons $i1$ != $i2$

J'espère que cela t'aides. P.s. Les catalogues globaux sont la fonctionnalité de Resharper 6.1 (EAP), sera marquée comme finale très bientôt.

Mise à jour : J'ai déposé A problème de resharper pour marquer toutes les interfaces '==' un avertissement, sauf s'il s'agit de comparer à NULL. S'il vous plaît voter si vous pensez que c'est une fonctionnalité digne.

update2 : Resharper a également un attribut [NaTAppLYePlyealityPeritor] qui peut aider.

4
Yurik