Question

Arrière-plan

J'utilise la programmation basée sur l'interface sur un projet en cours et se sont heurtées à un problème en cas de surcharge des opérateurs (en particulier les opérateurs égalité et d'inégalité).


Hypothèses

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

Mise à jour - Le Faux est la suite de l'Assomption

  • Exiger que toutes les comparaisons à l'utilisation Equals plutôt que l'opérateur == est pas une solution viable, en particulier lors du passage de vos types de bibliothèques (comme les collections).

La raison pour laquelle je suis préoccupé par exiger Equals à utiliser plutôt que l'opérateur == est que je ne trouve nulle part dans les lignes directrices .NET dont elle a indiqué qu'elle utiliserait Égal plutôt que l'opérateur == ou même suggérer. Cependant, après relecture Directives pour Redéfinition Égal et l'opérateur == I ont 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 mettre en œuvre l'opérateur == pour obtenir cette fonctionnalité. Lorsqu'un type est immuable, qui est, les données contenues dans l'instance ne peut pas être changé, la surcharge d'opérateur == pour comparer la valeur d'égalité au lieu de l'égalité de référence peut être utile parce que, comme des objets immuables, ils peuvent être considérés comme les mêmes aussi longtemps car ils ont la même valeur. Il n'est pas une bonne idée de remplacer == opérateur dans les types non immuables.

et cette Interface assimilables

  

L'interface IEquatable est utilisée par collection générique objets tels que Dictionnaire, liste et LinkedList lors du test pour l'égalité dans ces méthodes que contient, IndexOf, LastIndexOf et Supprimer. Il devrait être mis en œuvre pour tout objet qui pourrait être stocké dans une collection générique.


Contraints

  • Toute solution ne doit pas jeter les objets besoin de leurs interfaces à leurs types concrets.

Problème

  • Lorsque jamais les deux côtés des == opérateur sont une interface, aucun opérateur == signature de la méthode de surcharge des types de béton sous-jacent et correspond ainsi à l'opérateur par défaut de l'objet == méthode est appelée.
  • Lorsque la surcharge d'un opérateur sur une classe, au moins un des paramètres de l'opérateur binaire doit être du type contenant, sinon une erreur du compilateur est généré (Erreur BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx )
  • Il est impossible de préciser la mise en œuvre sur une interface

Voir ci-dessous le code et la sortie montrant la question.


Question

Comment avez-vous communiquiez votre opérateur pour surcharge vos classes lors de l'utilisation des programmes de base de l'interface?


Références

== Opérateur (Référence C #)

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


Voir aussi


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

Sortie

Address operator== overload called
Equal with both sides cast.
Était-ce utile?

La solution

Réponse courte: Je pense que votre deuxième hypothèse peut être erronée. Equals() est la bonne façon de vérifier les l'égalité sémantique de deux objets, et non operator ==.


Réponse longue:. Résolution de surcharge pour les opérateurs est effectué au moment de la compilation, exécutez pas le temps

À moins que le compilateur peut définitivement connaître les types des objets qu'il est l'application d'un opérateur, il ne compilera pas. Étant donné que le compilateur ne peut pas être sûr qu'un IAddress va être quelque chose qui a une dérogation pour == définie, il revient à la operator == par défaut de mise en œuvre System.Object.

Pour voir plus clairement, essayez de définir un operator + pour Address et en ajoutant deux instances IAddress. À moins que vous lancez explicitement Address, il ne parviendra pas à compiler. Pourquoi? Parce que le compilateur ne peut pas dire qu'un IAddress particulier est un Address, et il n'y a pas de mise en œuvre operator + par défaut pour retomber dans System.Object.


Une partie de votre frustration découle probablement du fait que Object met en œuvre un operator ==, et tout est un Object, de sorte que le compilateur peut résoudre avec succès les opérations comme a == b pour tous les types. Lorsque vous == redéfini, vous vous attendiez à voir le même comportement, mais n'a pas, et c'est parce que le meilleur match que le compilateur peut trouver est la mise en œuvre de Object d'origine.

  

Exiger que toutes les comparaisons à l'utilisation Equals plutôt que l'opérateur == est pas une solution viable, en particulier lors du passage de vos types de bibliothèques (comme les collections).

À mon avis, c'est exactement ce que vous devriez faire. Equals() est la bonne façon de vérifier les l'égalité sémantique de deux objets. Parfois, l'égalité sémantique est juste référence l'égalité, dans ce cas, vous aurez pas besoin de changer quoi que ce soit. Dans d'autres cas, comme dans votre exemple, vous substituez Equals lorsque vous avez besoin d'un contrat d'égalité plus forte que l'égalité de référence. Par exemple, vous pouvez envisager deux Persons égales si elles ont le même numéro de sécurité sociale, ou deux Vehicles égales 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 devez remplacer Equals(), mais presque jamais l'inverse. operator == est plus d'une commodité syntactique. Certaines langues CLR (par exemple de Visual Basic.NET) ne permettent même pas vous surcharger l'opérateur d'égalité.

Autres conseils

Nous avons couru dans le même problème, et on a trouvé une excellente solution. Modèles ReSharper personnalisés

Nous avons configuré tous nos utilisateurs d'utiliser un catalogue de modèle global commun en plus de leur propre, et l'a placé dans SVN afin qu'il puisse être mis à jour et versionné pour tout le monde.

Le catalogue comprend tous les modèles connus pour être mal dans notre système:

$i1$ == $i2$ (où sont i1 et i2 expressions de notre type d'interface, ou dérivée.

remplacer le motif est

$i1$.Equals($i2$)

et la gravité est "Afficher en erreur".

De même, nous avons $i1$ != $i2$

Espérons que cela aide. Post-scriptum Les catalogues globaux est la fonctionnalité dans ReSharper 6.1 (EAP), seront marqués comme très bientôt final.

Mise à jour : Je ReSharper Problème pour marquer toute interface « == » un avertissement à moins qu'il compare à null. S'il vous plaît voter si vous pensez qu'il est une caractéristique digne.

Update2 :. ReSharper a également [CannotApplyEqualityOperator] attribut qui peut aider

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top