28

私はこれらのデータ転送オブジェクトを持っています:

public class Report 
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    //and so on for many, many properties.
}

書きたくない

public bool areEqual(Report a, Report b)
{
    if (a.Id != b.Id) return false;
    if (a.ProjectId != b.ProjectId) return false;
    //Repeat ad nauseum
    return true;
}

プロパティのみを持つ2つのオブジェクトが同じ値を持っているかどうかをテストするより速い方法はありますか(プロパティごとに1行のコードまたは1つの論理式を必要としないものですか?)

構造体への切り替えはオプションではありません。

4

5 に答える 5

67

おそらくExpression.Compile()パフォーマンスのために使用して、いくつかの反射はどうですか?(ここで静的 ctor を使用すると、 ごとに 1 回だけコンパイルできることに注意してくださいT):

using System;
using System.Linq.Expressions;

public class Report {
    public int Id { get; set; }
    public int ProjectId { get; set; }
    static void Main() {
        Report a = new Report { Id = 1, ProjectId = 13 },
            b = new Report { Id = 1, ProjectId = 13 },
            c = new Report { Id = 1, ProjectId = 12 };
        Console.WriteLine(PropertyCompare.Equal(a, b));
        Console.WriteLine(PropertyCompare.Equal(a, c));
    }
}
static class PropertyCompare {
    public static bool Equal<T>(T x, T y) {
        return Cache<T>.Compare(x, y);
    }
    static class Cache<T> {
        internal static readonly Func<T, T, bool> Compare;
        static Cache() {
            var props = typeof(T).GetProperties();
            if (props.Length == 0) {
                Compare = delegate { return true; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            for (int i = 0; i < props.Length; i++) {
                var propEqual = Expression.Equal(
                    Expression.Property(x, props[i]),
                    Expression.Property(y, props[i]));
                if (body == null) {
                    body = propEqual;
                } else {
                    body = Expression.AndAlso(body, propEqual);
                }
            }
            Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
                          .Compile();
        }
    }
}

編集:フィールドも処理するように更新:

static class MemberCompare
{
    public static bool Equal<T>(T x, T y)
    {
        return Cache<T>.Compare(x, y);
    }
    static class Cache<T>
    {
        internal static readonly Func<T, T, bool> Compare;
        static Cache()
        {
            var members = typeof(T).GetProperties(
                BindingFlags.Instance | BindingFlags.Public)
                .Cast<MemberInfo>().Concat(typeof(T).GetFields(
                BindingFlags.Instance | BindingFlags.Public)
                .Cast<MemberInfo>());
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            foreach(var member in members)
            {
                Expression memberEqual;
                switch (member.MemberType)
                {
                    case MemberTypes.Field:
                        memberEqual = Expression.Equal(
                            Expression.Field(x, (FieldInfo)member),
                            Expression.Field(y, (FieldInfo)member));
                        break;
                    case MemberTypes.Property:
                        memberEqual = Expression.Equal(
                            Expression.Property(x, (PropertyInfo)member),
                            Expression.Property(y, (PropertyInfo)member));
                        break;
                    default:
                        throw new NotSupportedException(
                            member.MemberType.ToString());
                }
                if (body == null)
                {
                    body = memberEqual;
                }
                else
                {
                    body = Expression.AndAlso(body, memberEqual);
                }
            }
            if (body == null)
            {
                Compare = delegate { return true; };
            }
            else
            {
                Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
                              .Compile();
            }
        }
    }
}
于 2009-06-12T13:23:48.070 に答える
6

最初の回答は (質問 1831747 )

私のMemberwiseEqualityComparerをチェックして、ニーズに合っているかどうかを確認してください。

それは本当に使いやすく、非常に効率的です。IL-emit を使用して、最初の実行時に Equals および GetHashCode 関数全体を生成します (使用される型ごとに 1 回)。指定されたオブジェクトの各フィールド (プライベートまたはパブリック) を、その型 (EqualityComparer.Default) の既定の等値比較子を使用して比較します。しばらく本番環境で使用しており、安定しているように見えますが、保証はしません =)

独自の equals メソッドをロールしているときにめったに考えない厄介なエッジケースをすべて処理します (つまり、最初にオブジェクトにボックス化し、多くをオフにしない限り、独自のオブジェクトを null と比較することはできません)。その他の null 関連の問題)。

それについてブログ記事を書こうと思っていたのですが、まだできていません。コードは少し文書化されていませんが、気に入ったら少し整理できます。

public override int GetHashCode()
{
    return MemberwiseEqualityComparer<Foo>.Default.GetHashCode(this);
}

public override bool Equals(object obj)
{
    if (obj == null)
        return false;

    return Equals(obj as Foo);
}

public override bool Equals(Foo other)
{
    return MemberwiseEqualityComparer<Foo>.Default.Equals(this, other);
}

MemberwiseEqualityComparer はMIT ライセンスの下でリリースされており、ライセンスを少し変更せずにプロプライエタリ ソリューションで使用するなど、ほぼ何でも好きなことを行うことができます。

于 2009-12-02T10:10:10.717 に答える
4

私は Marc のコードを拡張して、私自身の使用のために本格的な IEqualityComparer 実装にしました。これは、将来他の人に役立つかもしれないと考えました。

/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that compares the values of each public property.
/// </summary>
/// <typeparam name="T"> The type to compare. </typeparam>
public class PropertyEqualityComparer<T> : IEqualityComparer<T>
{
    // http://stackoverflow.com/questions/986572/hows-to-quick-check-if-data-transfer-two-objects-have-equal-properties-in-c/986617#986617

    static class EqualityCache
    {
        internal static readonly Func<T, T, bool> Compare;
        static EqualityCache()
        {
            var props = typeof(T).GetProperties();
            if (props.Length == 0)
            {
                Compare = delegate { return true; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            for (int i = 0; i < props.Length; i++)
            {
                var propEqual = Expression.Equal(
                    Expression.Property(x, props[i]),
                    Expression.Property(y, props[i]));
                if (body == null)
                {
                    body = propEqual;
                }
                else
                {
                    body = Expression.AndAlso(body, propEqual);
                }
            }
            Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y).Compile();
        }
    }

    /// <inheritdoc/>
    public bool Equals(T x, T y)
    {
        return EqualityCache.Compare(x, y);
    }

    static class HashCodeCache
    {
        internal static readonly Func<T, int> Hasher;
        static HashCodeCache()
        {
            var props = typeof(T).GetProperties();
            if (props.Length == 0)
            {
                Hasher = delegate { return 0; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");

            Expression body = null;
            for (int i = 0; i < props.Length; i++)
            {
                var prop = Expression.Property(x, props[i]);
                var type = props[i].PropertyType;
                var isNull = type.IsValueType ? (Expression)Expression.Constant(false, typeof(bool)) : Expression.Equal(prop, Expression.Constant(null, type));
                var hashCodeFunc = type.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public);
                var getHashCode = Expression.Call(prop, hashCodeFunc);
                var hashCode = Expression.Condition(isNull, Expression.Constant(0, typeof(int)), getHashCode);

                if (body == null)
                {
                    body = hashCode;
                }
                else
                {
                    body = Expression.ExclusiveOr(Expression.Multiply(body, Expression.Constant(typeof(T).AssemblyQualifiedName.GetHashCode(), typeof(int))), hashCode);
                }
            }
            Hasher = Expression.Lambda<Func<T, int>>(body, x).Compile();
        }
    }

    /// <inheritdoc/>
    public int GetHashCode(T obj)
    {
        return HashCodeCache.Hasher(obj);
    }
}
于 2013-05-14T13:58:16.743 に答える
2

残念ながら、フィールド値を比較するためのメソッドを作成する必要があります。 System.ValueTypeはリフレクションを使用してのフィールド値を比較するように構築されていますが、structパフォーマンスが遅いため、これでもお勧めできません。最善の方法は、メソッドをオーバーライドし、強く型付けされたオーバーロードEqualsのインターフェイスを実装することです。IEquatable<T>Equals

その間、実装GetHashCodeを補完するために適切なオーバーライドを提供することもできます。Equalsこれらの手順はすべて、グッドプラクティスと見なされます。

于 2009-06-12T13:15:50.213 に答える
1

これを行うにはリフレクションを使用する必要があります。このリンクをたどってください-> c#でのオブジェクトプロパティの比較

于 2009-06-12T13:37:18.397 に答える