76 votes

Surcharge des opérateurs basé sur l’Interface de programmation en c#

Arrière-plan

Je suis en utilisant l'interface de programmation basé sur un projet en cours et ont rencontré un problème lors de la surcharge des opérateurs (plus précisément de l'Égalité et de l'Inégalité des opérateurs).


Hypothèses

  • Je suis à l'aide de C# 3.0, .NET 3.5 et Visual Studio 2008

Mise à JOUR - L'Hypothèse Suivante a été Faux!

  • Exigeant que toutes les comparaisons à utilisation Égale, plutôt que de 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 j'étais inquiète au sujet de l'obligation est Égale à utiliser plutôt que l'opérateur== est que je ne pouvais pas trouver n'importe où dans le .NET des lignes directrices qu'il a déclaré qu'il utiliserait est Égal plutôt que l'opérateur==, ou même suggérer. Cependant, après re-lecture de lignes Directrices pour l'Impérieuse d'égal à Égal et de l'Opérateur== j'ai trouvé ceci:

Par défaut, l'opérateur == tests de référence de l'égalité de déterminer si deux références indiquent le même objet. Par conséquent, les types de référence n'ont pas à mettre en œuvre l'opérateur == pour obtenir cette fonctionnalité. Quand un type est immuable, qui est, les données contenues dans l'instance ne peut pas être changé, la surcharge de l'opérateur == pour comparer la valeur de l'égalité au lieu de référence de l'égalité peut être utile parce que, comme des objets immuables, ils peuvent être considérés de la même aussi longtemps qu'ils ont la même valeur. Ce n'est pas une bonne idée de remplacer l'opérateur == non immuable types.

et ce Equatable Interface

Le IEquatable interface est utilisée par générique des objets de collection tels que le Dictionnaire, la Liste et les LinkedList lors de l'essai pour l'égalité dans les méthodes telles que Contient, IndexOf, LastIndexOf, et Retirez-la. Il devrait être mis en œuvre pour n'importe quel objet qui peut être stocké dans une collection générique.


Les contraintes

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


Problème

  • Quand jamais les deux côtés de l'opérateur== sont une interface, aucun opérateur== surcharge de la signature de la méthode de la sous-jacentes types de béton match et donc la valeur par défaut de l'Objet de l'opérateur== méthode sera appelée.
  • En cas de surcharge d'un opérateur sur une classe, au moins l'un des paramètres de l'opérateur binaire doit être du type contenant, sinon une erreur de compilateur est produite (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 de Sortie ci-dessous montrant le problème.


Question

Comment voulez-vous assurer la surcharge de l'opérateur pour vos classes lors de l'utilisation de l'interface de base de la programmation?


Références

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

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


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.

60voto

John Feminella Points 116878

Réponse courte: je pense que votre deuxième hypothèse peut être imparfaite. Equals() est le bon moyen de vérifier la sémantique de l'égalité de deux objets, pas d' operator ==.


Réponse longue: résolution de Surcharge pour les opérateurs est effectué au moment de la compilation, pas de temps d'exécution.

À moins que le compilateur peut définitivement connaître les types des objets, c'est l'application d'un opérateur, il ne compile pas. Car le compilateur ne peut pas être sûr que l' IAddress va être quelque chose qui a un remplacement pour == défini, il revient à la valeur par défaut operator == de la mise en œuvre de l' System.Object.

Pour le voir plus clairement, essayez de définir une operator + pour Address et l'ajout de deux IAddress des cas. Sauf si vous avez explicitement exprimées Address, il ne pourra pas compiler. Pourquoi? Parce que le compilateur ne peut pas dire qu'un particulier IAddress est Address, et il n'y a pas de valeur par défaut operator + de mise en œuvre de retomber en System.Object.


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

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

De mon point de vue, c'est précisément ce que vous devriez faire. Equals() est le bon moyen de vérifier la sémantique de l'égalité de deux objets. Parfois sémantique de l'égalité est tout simplement référence à l'égalité, dans ce cas, vous n'aurez pas besoin de changer quoi que ce soit. Dans d'autres cas, comme dans votre exemple, vous allez remplacer Equals quand vous avez besoin d'une plus forte égalité contrat de référence de l'égalité. Par exemple, vous pourriez envisager de 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 avez besoin de redéfinir operator ==, vous devez remplacer Equals(), mais presque jamais l'inverse. operator == est plus d'une syntaxe de commodité. Certains CLR langues (par exemple, Visual Basic.NET) ne pas même vous permettre de remplacer l'opérateur d'égalité.

4voto

Yurik Points 2005

Nous avons rencontré le même problème et trouvé une excellente solution: Resharper modèles personnalisés.

Nous avons configuré l'ENSEMBLE de nos utilisateurs à utiliser un commun mondial, catalogue de motif, en plus de leur propre, et l'a placé dans le SVN de sorte qu'il peut être versionnés et mis à jour pour tout le monde.

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

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

remplacer le modèle est

$i1$.Equals($i2$)

et la gravité est de "Montrer que l'erreur".

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

Espérons que cette aide. P. S. catalogues Globaux, est la caractéristique dans Resharper 6.1 (PAE), sera marqué comme final très bientôt.

Mise à jour: j'ai déposé une Resharper Question de marquer l'ensemble de l'interface '==' un avertissement, sauf si c'est la comparaison à la valeur null. Merci de voter si vous pensez qu'il est digne de la fonction.

Update2: Resharper a aussi [CannotApplyEqualityOperator] attribut qui peut vous aider.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X