2010-06-17 4 views
11

Étant donné une classe avec 35 champs et 2 objets avec un certain nombre de valeurs de champs différents. Existe-t-il un moyen astucieux d'obtenir une liste < Chaîne> avec le nom des champs où l'objet est comme suit?Comparer 2 objets et récupérer une liste de champs avec des valeurs différentes

par exemple.

obj1.Name = "aaa"; 
obj1.LastName = "bbb"; 
obj1.Address = "xcs"; 
obj2.Name = "aaa"; 
obj2.LastName = "ccc"; 
obj2.Address = "jk"; 
objectif

:

list<<String>String> containing 2 Strings LastName and Address 

Je vois la réflexion que le chemin à parcourir, mais avec 35 champs je crains qu'il ne soit trop lourd. Une autre idée, comme linq?

+1

Le coût de la réflexion est relatif. À quelle fréquence faites-vous cela? Il y a ** des façons ** de le faire beaucoup plus vite, mais si vous ne le faites pas dans une boucle serrée, la réflexion devrait être bonne (et simple). –

+0

(par exemple, il serait possible d'écrire ceci via ILGenerator ou Expression - mais la complexité est-elle justifiée? Faites le moi savoir ...) –

+0

Marc, je le fais dans WCF mais il sera utilisé par quelques utilisateurs, alors j'ai décidé de réfléchir. Merci beaucoup pour vos efforts. a – ajj

Répondre

1

La réflexion est la voie à suivre et je ne pense pas que 35 champs soit un problème.

(Après m'être complètement déconcentré, je reviens à la pensée que je comprends la question et que la réflexion me conviendrait).

34

OK; c'est incroyablement plus d'effort que je ferais normalement, mais cela pourrait être une méthode utilitaire utile. Il crée IL à la volée (mises en cache) pour faire le travail, la manipulation d'objets valeur de type vs type ref, l'égalité intrinsèque de l'IL, les opérateurs d'égalité (==), et EqualityComparer<T> pour le reste:

using System.Collections.Generic; 
using System.Reflection; 
using System.Reflection.Emit; 

namespace ConsoleApplication2 
{ 
    using System; 
    class Program 
    { 
     static void Main() 
     { 
      WriteDeltas(new Foo {X = 123, Y = DateTime.Today, Z = null}, 
         new Foo {X = 124, Y = DateTime.Today, Z = null}); 
      WriteDeltas(new Foo { X = 123, Y = DateTime.Today, Z = null }, 
         new Foo { X = 123, Y = DateTime.Now, Z = new Dummy()}); 
      WriteDeltas(new Bar { X = 123, Y = DateTime.Today, Z = null }, 
         new Bar { X = 124, Y = DateTime.Today, Z = null }); 
      WriteDeltas(new Bar { X = 123, Y = DateTime.Today, Z = null }, 
         new Bar { X = 123, Y = DateTime.Now, Z = new Dummy() }); 
     } 
     static void WriteDeltas<T>(T x, T y) 
     { 
      Console.WriteLine("----"); 
      foreach(string delta in PropertyComparer<T>.GetDeltas(x,y)) 
      { 
       Console.WriteLine(delta); 
      } 

     } 

    } 
    class Dummy {} 
    class Foo 
    { 
     public int X { get; set; } 
     public DateTime Y { get; set; } 
     public Dummy Z { get; set; } 
    } 
    struct Bar 
    { 
     public int X { get; set; } 
     public DateTime Y { get; set; } 
     public Dummy Z { get; set; } 
    } 

    public static class PropertyComparer<T> 
    { 
     private static readonly Func<T, T, List<string>> getDeltas; 
     static PropertyComparer() 
     { 
      var dyn = new DynamicMethod(":getDeltas", typeof (List<string>), new[] {typeof (T), typeof (T)},typeof(T)); 
      var il = dyn.GetILGenerator(); 
      il.Emit(OpCodes.Newobj, typeof (List<string>).GetConstructor(Type.EmptyTypes)); 
      bool isValueType = typeof (T).IsValueType; 
      OpCode callType = isValueType ? OpCodes.Call : OpCodes.Callvirt; 
      var add = typeof(List<string>).GetMethod("Add"); 
      foreach (var prop in typeof(T).GetProperties()) 
      { 
       if (!prop.CanRead) continue; 
       Label next = il.DefineLabel(); 
       switch (Type.GetTypeCode(prop.PropertyType)) 
       { 
        case TypeCode.Boolean: 
        case TypeCode.Byte: 
        case TypeCode.Char: 
        case TypeCode.Double: 
        case TypeCode.Int16: 
        case TypeCode.Int32: 
        case TypeCode.Int64: 
        case TypeCode.SByte: 
        case TypeCode.Single: 
        case TypeCode.UInt16: 
        case TypeCode.UInt32: 
        case TypeCode.UInt64: 
         if(isValueType) {il.Emit(OpCodes.Ldarga_S, (byte)0);} else {il.Emit(OpCodes.Ldarg_0);} 
         il.EmitCall(callType, prop.GetGetMethod(), null); 
         if (isValueType) { il.Emit(OpCodes.Ldarga_S, (byte)1); } else { il.Emit(OpCodes.Ldarg_1); } 
         il.EmitCall(callType, prop.GetGetMethod(), null); 
         il.Emit(OpCodes.Ceq); 
         break; 
        default: 
         var pp = new Type[] {prop.PropertyType, prop.PropertyType}; 
         var eq = prop.PropertyType.GetMethod("op_Equality", BindingFlags.Public | BindingFlags.Static, null, pp, null); 
         if (eq != null) 
         { 
          if (isValueType) { il.Emit(OpCodes.Ldarga_S, (byte)0); } else { il.Emit(OpCodes.Ldarg_0); } 
          il.EmitCall(callType, prop.GetGetMethod(), null); 
          if (isValueType) { il.Emit(OpCodes.Ldarga_S, (byte)1); } else { il.Emit(OpCodes.Ldarg_1); } 
          il.EmitCall(callType, prop.GetGetMethod(), null); 
          il.EmitCall(OpCodes.Call, eq, null); 

         } 
         else 
         { 
          il.EmitCall(OpCodes.Call, typeof(EqualityComparer<>).MakeGenericType(prop.PropertyType).GetProperty("Default").GetGetMethod(), null); 
          if (isValueType) { il.Emit(OpCodes.Ldarga_S, (byte)0); } else { il.Emit(OpCodes.Ldarg_0); } 
          il.EmitCall(callType, prop.GetGetMethod(), null); 
          if (isValueType) { il.Emit(OpCodes.Ldarga_S, (byte)1); } else { il.Emit(OpCodes.Ldarg_1); } 
          il.EmitCall(callType, prop.GetGetMethod(), null); 
          il.EmitCall(OpCodes.Callvirt, typeof(EqualityComparer<>).MakeGenericType(prop.PropertyType).GetMethod("Equals", pp), null); 
         } 
         break; 
       } 
       il.Emit(OpCodes.Brtrue_S, next); // equal 
       il.Emit(OpCodes.Dup); 
       il.Emit(OpCodes.Ldstr, prop.Name); 
       il.EmitCall(OpCodes.Callvirt, add, null); 
       il.MarkLabel(next); 
      } 
      il.Emit(OpCodes.Ret); 
      getDeltas = (Func<T, T, List<string>>)dyn.CreateDelegate(typeof (Func<T, T, List<string>>)); 
     } 
     public static List<string> GetDeltas(T x, T y) { return getDeltas(x, y); } 

    } 
} 
+1

C'est génial. Serait-il difficile de rendre cet utilitaire récursif (c'est-à-dire: en parcourant le graphe d'objets)? :-) –

+0

@Troy - pour les articles simples, pas mal. Les listes sont une douleur. –

Questions connexes