Comparer toutes les propriétés d'un objet à l'aide d'arbres d'expression

.net c# dynamic-method expression-trees

Question

J'essaie d'écrire un générateur simple qui utilise un arbre d'expression pour générer de manière dynamique une méthode qui compare toutes les propriétés d'une instance d'un type aux propriétés d'une autre instance de ce type. Cela fonctionne bien pour la plupart des propriétés, comme int une string , mais échoue pour DateTime? (et vraisemblablement d’autres types de valeur nullable).

La méthode:

static Delegate GenerateComparer(Type type)
{
  var left = Expression.Parameter(type, "left");
  var right = Expression.Parameter(type, "right");

  Expression result = null;

  foreach (var p in type.GetProperties())
  {
    var leftProperty = Expression.Property(left, p.Name);
    var rightProperty = Expression.Property(right, p.Name);

    var equals = p.PropertyType.GetMethod("Equals", new[] { p.PropertyType });

    var callEqualsOnLeft = Expression.Call(leftProperty, equals, rightProperty);

    result = result != null ? (Expression)Expression.And(result, callEqualsOnLeft) : (Expression)callEqualsOnLeft;
  }

  var method = Expression.Lambda(result, left, right).Compile();

  return method;

}

Sur un DateTime? propriété il échoue avec:

L'expression de type 'System.Nullable`1 [System.DateTime]' ne peut pas être utilisée pour le paramètre de type 'System.Object' de la méthode 'Boolean Equals (System.Object)'

OK, alors il trouve une surcharge d' Equals qui attend un object . Alors, pourquoi ne puis-je pas passer un DateTime? dans cela, comme il est convertible en object ? Si je regarde Nullable<T> , il y a bien un remplacement d' Equals(object o) .

PS : Je réalise que ce n'est pas encore un bon générateur car il ne peut pas gérer null valeurs null , mais j'y viendrai :)

MISE À JOUR : La réponse d'Iraklis a fonctionné pour ce problème particulier, mais au final, j'ai opté pour une approche beaucoup plus simple qui, à mon avis, suffit: utilisez simplement Expression.Equal . Je pense que cela couvre 99% de mes cas (je ne sais pas s'il peut gérer le dépassement d' Equals sans le == , mais ce n'est pas grave).

Réponse acceptée

cela peut fonctionner si vous vérifiez que les types sont nullables avec ce code:

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)){}  

L'exemple de code est d' ici
Et s’ils sont Nullables, vous pouvez appeler

Nullable.Equals<T>(T? n1, T? n2);

Réponse populaire

Après avoir cherché quelque chose que je puisse utiliser sur le Web, j'ai décidé de le mettre en œuvre moi aussi. Je n'ai pas utilisé d'arbres d'expression. Au lieu de cela, j'utilise réflexion pour analyser toutes les propriétés et ToString() pour les comparer. Si la propriété est une collection, tous les éléments de la collection seront comparés.

Voici le code;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace Utils
{
    public class PropertyComparer<T> : IEqualityComparer<T>
    {
        public bool Equals(T x, T y)
        {
            IEnumerable<PropertyInfo> allProperties = typeof(T).GetProperties();
            foreach(PropertyInfo pi in allProperties)
            {
                if (pi.GetCustomAttributes<EqualityIrrelevantAttribute>().Any())
                {
                    continue;
                }

                object xProp = pi.GetValue(x);
                object yProp = pi.GetValue(y);

                if ((xProp == null) && (yProp == null))
                {
                    continue;
                }
                else if ((xProp == null) || (yProp == null))
                {
                    return false;
                }
                else if (xProp is ICollection)
                {
                    if (!CollectionsEqual(xProp as ICollection, yProp as ICollection))
                    {
                        return false;
                    }
                }

                if (xProp.ToString() != yProp.ToString())
                {
                    return false;
                }
            }

            return true;
        }

        bool CollectionsEqual(ICollection left, ICollection right)
        {
            IEnumerator leftEnumerator = left.GetEnumerator();
            IEnumerator rightEnumerator = right.GetEnumerator();

            bool leftAdvanced = leftEnumerator.MoveNext();
            bool rightAdvanced = rightEnumerator.MoveNext();

            if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced))
            {
                return false;
            }
            else if (!leftAdvanced && !rightAdvanced)
            {
                return true;
            }

            bool compareByClass = false;
            object comparer = null;
            MethodInfo equalsMethod = null;

            // Inspect type first
            object peek = leftEnumerator.Current;
            Type valuesType = peek.GetType();
            if (valuesType.IsClass)
            {
                compareByClass = true;
                Type comparerType = typeof(PropertyComparer<>).MakeGenericType(new Type[] { valuesType });
                equalsMethod = comparerType.GetMethod("Equals", new Type[] { valuesType, valuesType });
                comparer = Activator.CreateInstance(comparerType);
            }


            leftEnumerator.Reset();
            rightEnumerator.Reset();

            while (true)
            {
                leftAdvanced = leftEnumerator.MoveNext();
                rightAdvanced = rightEnumerator.MoveNext();

                if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced))
                {
                    return false;
                }
                else if (!leftAdvanced && !rightAdvanced)
                {
                    return true;
                }

                object leftValue = leftEnumerator.Current;
                object rightValue = rightEnumerator.Current;

                if (compareByClass)
                {
                    bool result = (bool)equalsMethod.Invoke(comparer, new object[] { leftValue, rightValue });
                    if (!result)
                    {
                        return false;
                    }
                }
                else if (leftEnumerator.Current.ToString() != rightEnumerator.Current.ToString())
                {
                    return false;
                }

                // Continue looping
            }
        }

        public int GetHashCode(T obj)
        {
            throw new NotImplementedException();
        }
    }
}

Si une propriété d'une classe est elle-même une classe, elle créera un nouveau comparateur capable de comparer les propriétés de cette classe. Vous pouvez également éventuellement indiquer que des propriétés particulières sont exclues de la comparaison à l'aide de EqualityIrrelevantAttribute . Cela fonctionne vraiment bien pour moi, j'espère que d'autres le trouveront utile.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi