C#中如何快速检查两个数据传输对象是否具有相同的属性?

发布于 2024-07-25 06:41:09 字数 480 浏览 4 评论 0原文

我有这些数据传输对象:

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;
}

是否有一种更快的方法来测试两个仅具有属性的对象是否具有相同的值(不需要一行代码或每个属性一个逻辑表达式的东西?)

切换到结构不是一个选择。

I have these data transfer objects:

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

I don't want to write

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;
}

Is there a faster way to test if two object with only properties have the same values (something that doesn't require one line of code or one logical expression per property?)

Switching to structs is not an option.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

没有你我更好 2024-08-01 06:41:09

一些反思怎么样,也许使用Expression.Compile()来提高性能? (注意这里的静态构造函数确保我们每个 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();
            }
        }
    }
}

How about some reflection, perhaps using Expression.Compile() for performance? (note the static ctor here ensures we only compile it once per 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();
        }
    }
}

Edit: updated to handle fields too:

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();
            }
        }
    }
}
慕巷 2024-08-01 06:41:09

最初回答于(问题 1831747)

查看我的MemberwiseEqualityComparer 看看如果它符合您的需求。

它真的很容易使用而且非常高效。 它使用 IL-emit 在第一次运行时生成整个 Equals 和 GetHashCode 函数(每个使用的类型一次)。 它将使用该类型的默认相等比较器 (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 许可证 发布的,这意味着您几乎可以用它做任何您想做的事情,包括在专有解决方案中使用它,而无需稍微改变您的许可。

Originally answered at (question 1831747)

Check out my MemberwiseEqualityComparer to see if it fits your needs.

It's really easy to use and quite efficient too. It uses IL-emit to generate the entire Equals and GetHashCode function on the first run (once for each type used). It will compare each field (private or public) of the given object using the default equality comparer for that type (EqualityComparer.Default). We've been using it in production for a while and it seems stable but I'll leave no guarantees =)

It takes care of all those pescy edge-cases that you rarely think of when you're rolling your own equals method (ie, you can't comparer your own object with null unless you've boxed it in an object first and lot's off more null-related issues).

I've been meaning to write a blog post about it but haven't gotten around to it yet. The code is a bit undocumented but if you like it I could clean it up a bit.

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);
}

The MemberwiseEqualityComparer is released under the MIT license meaining you can do pretty much whatever you want with it, including using it in proprietary solutions without changing you licensing a bit.

悲凉≈ 2024-08-01 06:41:09

我已将 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);
    }
}

I've extended Marc's code to be a fully-fledged IEqualityComparer implementation for my own uses, and thought this may be useful to others in the future:

/// <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);
    }
}
有木有妳兜一样 2024-08-01 06:41:09

不幸的是,您将不得不编写方法来比较字段值。 System.ValueType 旨在使用反射并比较 struct 的字段值,但由于性能较低,即使这样做也是不可取的。 最好的办法是重写 Equals 方法并实现 IEquatable接口,用于强类型 Equals 重载。

当您这样做时,您也可以提供一个好的 GetHashCode 重写来补充 Equals 实现。 所有这些步骤都被认为是良好实践。

Unfortunately you are going to have to write the method to compare the field values. System.ValueType is built to use reflection and compare the field values of a struct but even this is unadvisable due to slow performance. The best thing to do is to override the Equals method and also implement the IEquatable<T> interface for a strongly typed Equals overload.

While you are at it, you might as well provide a good GetHashCode override as well to complement the Equals implementation. All of these steps are considered good practice.

黑色毁心梦 2024-08-01 06:41:09

进行比较的现代方法是使用 record

public record Report(int Id, int ProjectId, ...);

然后使用 == 进行简单比较:

var report1 = new Report { Id = 1, ProjectId = 100 };
var report2 = new Report { Id = 1, ProjectId = 100 };

bool areEqual = report1 == report2; // true

The modern way to do the comparison is by using record:

public record Report(int Id, int ProjectId, ...);

Then a simple comparison using ==:

var report1 = new Report { Id = 1, ProjectId = 100 };
var report2 = new Report { Id = 1, ProjectId = 100 };

bool areEqual = report1 == report2; // true
相守太难 2024-08-01 06:41:09

您将需要使用反射来执行此操作,请点击此链接 --> 比较 C# 中的对象属性

You will need to use reflection to do this, please follow this link --> Comparing object properties in c#

断念 2024-08-01 06:41:09

简单解决方案

string output1 = JsonConvert.SerializeObject(input1);
string output2 = JsonConvert.SerializeObject(input2);

if(output1 == output2)
{
  // Do something 
}

Simpel solution

string output1 = JsonConvert.SerializeObject(input1);
string output2 = JsonConvert.SerializeObject(input2);

if(output1 == output2)
{
  // Do something 
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文