none
Test d'égalité de deux objets RRS feed

  • Question

  • Bonjour,

    En C# 4.0, je suis bien étonné par le résultat du test d'égalité de deux objets identiques.

    Je m'explique, voici la code :

      public class tab
      {
        UInt16[] x = new UInt16[] { 1, 2, 3 };
      }
    
      class maclasse
      {
        void test()
        {
          tab v1 = new tab();
          tab v2 = new tab();
          Console.WriteLine("Object.Equals(\"{0}\", \"{1}\") => {2}", v1, v2, Object.Equals(v1, v2));
          Console.WriteLine("Object.Equals(\"{0}\", \"{1}\") => {2}", v1, v2, v1.Equals(v2));
        }
      }
    
    

    Et voici ce qui s'imprime sur la console :

    Object.Equals("Test.tab", "Test.tab") => False

    Object.Equals("Test.tab", "Test.tab") => False

     

    Comment expliquer que le résultat soit False alors que les deux objets sont identiques ?

    Comment tester l'égalité de deux objets aussi complexes que ceux-ci ?

    Merci de vos lumières.


    Alain
    samedi 12 février 2011 22:12

Réponses

  • Bonjour Alain,

    vos deux objets ne sont pas identiques dans le sens ou il s'agit de deux instances différentes : il existe bien deux objets différents V1 et V2 en mémoire.

    C'est le comportement standard attendu. Vous auriez égalité les si 2 variables v1 et v2 pointait vers la même référence.

    Si vous voulez faire en sorte que la méthode Equals renvoi true si le contenu des tableaux est identique il vous faut surcharger la méthode Equals au sein de la classe tab.

    Par exemple :

    public override bool Equals(object obj)
    {
     tab tabtest = obj as tab;
     if (tabtest != null)
     {
     return this.x.SequenceEqual(tabtest.x);
     }
     else
     {
     return false;
     }
    }
    

     

    Par ailleurs il faut normalement également surcharger la méthode GetHashCode si vous surcharger la méthode Equals ( voir Equals ).

    Suite à cette surcharge vous pourriez néanmoins être étonné du comportement au sein d'une liste générique. En effet pour ces listes vous devez en plus implémenter l'interface IEquatable<T> sur votre objet.

    Une remarque : les surcharges modifiant par nature le comportement par défaut peuvent donc être "surprenante" pour le développeur reprenant votre code ...

    Cordialement

    • Marqué comme réponse AchLog mardi 15 février 2011 10:19
    lundi 14 février 2011 17:48
    Modérateur
  • Bonsoir Alain,

    voici une autre idée pour réaliser votre test. A  fin d'éviter d'ecrire et de répeter le code de test des éléments du tableau, il s'agit d'utiliser la méthode d'extension SequenceEquals. Elle permet dejà tester si les séquence sont identiques ( en utilisant la comparaison par défaut des éléments ).

    Par contre pour appeler cette méthode générique sans connaitre le type des tableaux à la compilation, l'appel de la méthode est effectuée par reflexion.

    Cordialement

    public class MetaEquals
    {
     public static bool ItemsEquals(Object o1, Object o2)
     {
      if (o1 == null || o2 == null)
       return false;
    
      Type t1 = o1.GetType();
      Type t2 = o2.GetType();
      bool retour = false;
    
      // il s'agit de 2 tableaux 
      if (o1 is Array && o2 is Array)
      {
       // les tableaux sont de même type
       if (t1.IsInstanceOfType(o2) && t2.IsInstanceOfType(o1))
       {
        // appel de la méthode via reflexion pour passer le type en paramètre
        MethodInfo method = typeof(MetaEquals).GetMethod("ArrayEquals", BindingFlags.Static | BindingFlags.NonPublic);
        MethodInfo generic = method.MakeGenericMethod(t1.GetElementType());
        retour = (bool)generic.Invoke(null, new[] { o1, o2 });
       }
      }
      else
      {
       // comparaison par défaut
       retour = object.Equals(o1, o2);
      }
    
      return retour;
     }
    
     private static Boolean ArrayEquals<T>(T[] o1, T[] o2)
     {
      return o1.SequenceEqual(o2);
     }
    }
    
    
    • Marqué comme réponse AchLog jeudi 17 février 2011 21:17
    jeudi 17 février 2011 18:12
    Modérateur

Toutes les réponses

  • Bonjour Alain,

    vos deux objets ne sont pas identiques dans le sens ou il s'agit de deux instances différentes : il existe bien deux objets différents V1 et V2 en mémoire.

    C'est le comportement standard attendu. Vous auriez égalité les si 2 variables v1 et v2 pointait vers la même référence.

    Si vous voulez faire en sorte que la méthode Equals renvoi true si le contenu des tableaux est identique il vous faut surcharger la méthode Equals au sein de la classe tab.

    Par exemple :

    public override bool Equals(object obj)
    {
     tab tabtest = obj as tab;
     if (tabtest != null)
     {
     return this.x.SequenceEqual(tabtest.x);
     }
     else
     {
     return false;
     }
    }
    

     

    Par ailleurs il faut normalement également surcharger la méthode GetHashCode si vous surcharger la méthode Equals ( voir Equals ).

    Suite à cette surcharge vous pourriez néanmoins être étonné du comportement au sein d'une liste générique. En effet pour ces listes vous devez en plus implémenter l'interface IEquatable<T> sur votre objet.

    Une remarque : les surcharges modifiant par nature le comportement par défaut peuvent donc être "surprenante" pour le développeur reprenant votre code ...

    Cordialement

    • Marqué comme réponse AchLog mardi 15 février 2011 10:19
    lundi 14 février 2011 17:48
    Modérateur
  • Merci Nikho pour votre réponse très claire.

    A vrai dire, j'y avais déjà un peu pensé mais ce qui m'a fait douter c'est qu'il existe aussi la méthode ReferenceEquals dont je pensais qu'elle testait justement l'égalité de deux références d'instances.

    Mais bon !

    Bien cordialement.

     


    Alain
    mardi 15 février 2011 09:14
  • La méthode ReferenceEquals est une méthode statique et ne peut donc être surcharger. Cette méthode vous permet par exemple de vous assurer que les références sont bien identiques dans le cas ou l'opérateur == a été surchargé.

    Cordialement

     

    mardi 15 février 2011 09:59
    Modérateur
  • Ok Nikho.

    Merci beaucoup de votre réponse.

    Voilà, en définitive comment j'ai résolu le problème. Merci de vos commentaires :

      public class MetaEquals
      {
        private static string[] TypeName = new string[] {"Int16[]","Int32[]","Int64[]","UInt16[]","UInt32[]","UInt64[]","Byte[]"};
    
    
        private static Boolean IsItemArray(Object obj)
        {
          return TypeName.Contains(obj.GetType().Name);
        }
        private static Boolean ValueEquals(Object o1, Object o2)
        {
          if (Equals(o1, o2)) { return true; }
    
          string type = o1.GetType().Name;
          if (o2.GetType().Name != type) return false;
    
          switch (type)
          {
            case "Int16[]":
              {
                Int16[] v1 = o1 as Int16[];
                Int16[] v2 = o2 as Int16[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
            case "Int32[]":
              {
                Int32[] v1 = o1 as Int32[];
                Int32[] v2 = o2 as Int32[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
    
            case "Int64[]":
              {
                Int64[] v1 = o1 as Int64[];
                Int64[] v2 = o2 as Int64[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
    
            case "UInt16[]":
              {
                UInt16[] v1 = o1 as UInt16[];
                UInt16[] v2 = o2 as UInt16[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
    
            case "UInt32[]":
              {
                UInt32[] v1 = o1 as UInt32[];
                UInt32[] v2 = o2 as UInt32[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
    
            case "UInt64[]":
              {
                UInt64[] v1 = o1 as UInt64[];
                UInt64[] v2 = o2 as UInt64[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
    
            case "Byte[]":
               {
                Byte[] v1 = o1 as Byte[];
                Byte[] v2 = o2 as Byte[];
                if (v1.Length != v2.Length) return false;
                for (int i = 0; i < v1.Length; i++) { if (v1[i] != v2[i]) return false; }
                return true;
              }
    
            default:
              return false;
          }
        }
    
        public static Boolean ItemsEquals(Object o1, Object o2)
        {
          if (IsItemArray(o1))
          {
            if (IsItemArray(o2)) { return ValueEquals(o1, o2); }
            else { return false; }
          }
          else
          {
            return Equals(o1, o2);
          }
        }
    
      }
    
    

    Bien cordialement.


    Alain
    mardi 15 février 2011 10:26
  • Bonsoir Alain,

    voici une autre idée pour réaliser votre test. A  fin d'éviter d'ecrire et de répeter le code de test des éléments du tableau, il s'agit d'utiliser la méthode d'extension SequenceEquals. Elle permet dejà tester si les séquence sont identiques ( en utilisant la comparaison par défaut des éléments ).

    Par contre pour appeler cette méthode générique sans connaitre le type des tableaux à la compilation, l'appel de la méthode est effectuée par reflexion.

    Cordialement

    public class MetaEquals
    {
     public static bool ItemsEquals(Object o1, Object o2)
     {
      if (o1 == null || o2 == null)
       return false;
    
      Type t1 = o1.GetType();
      Type t2 = o2.GetType();
      bool retour = false;
    
      // il s'agit de 2 tableaux 
      if (o1 is Array && o2 is Array)
      {
       // les tableaux sont de même type
       if (t1.IsInstanceOfType(o2) && t2.IsInstanceOfType(o1))
       {
        // appel de la méthode via reflexion pour passer le type en paramètre
        MethodInfo method = typeof(MetaEquals).GetMethod("ArrayEquals", BindingFlags.Static | BindingFlags.NonPublic);
        MethodInfo generic = method.MakeGenericMethod(t1.GetElementType());
        retour = (bool)generic.Invoke(null, new[] { o1, o2 });
       }
      }
      else
      {
       // comparaison par défaut
       retour = object.Equals(o1, o2);
      }
    
      return retour;
     }
    
     private static Boolean ArrayEquals<T>(T[] o1, T[] o2)
     {
      return o1.SequenceEqual(o2);
     }
    }
    
    
    • Marqué comme réponse AchLog jeudi 17 février 2011 21:17
    jeudi 17 février 2011 18:12
    Modérateur
  • Ok Nikho, merci beaucoup pour cet exercice de réflexion... Très utile, car je ne l'avais encore jamais utilisée. L'exemple tombe donc bien à pic.

    La méthode est beaucoup plus simple que la mienne, qui est un peu bestiale je le reconnais. Mais, paradoxalement, celle-ci n'est guère plus rapide à l'exécution. C'est dommage !

    Bien cordialement

     


    Alain
    jeudi 17 février 2011 21:23