Как быстро проверить, имеют ли два объекта передачи данных одинаковые свойства в C#?
Вопрос
У меня есть эти объекты передачи данных:
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;
}
Есть ли более быстрый способ проверить, имеют ли два объекта только со свойствами одинаковые значения (что-то, что не требует одной строки кода или одного логического выражения для каждого свойства?)
Переход на структуры не вариант.
Решение
Как насчет некоторого размышления, возможно, используя 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();
}
}
}
}
Другие советы
Первоначально ответ на (вопрос 1831747)
зацени мой Почленный метод сравнения равенства чтобы увидеть, соответствует ли он вашим потребностям.
Он действительно прост в использовании и довольно эффективен.Он использует IL-emit для генерации всей функции Equals и GetHashCode при первом запуске (по одному разу для каждого используемого типа).Он будет сравнивать каждое поле (частное или общедоступное) данного объекта, используя компаратор равенства по умолчанию для этого типа (EqualityComparer.Default).Мы уже некоторое время используем его в производстве, и он кажется стабильным, но я не оставлю никаких гарантий =)
Он позаботится обо всех тех неприятных крайних случаях, о которых вы редко думаете, когда используете свой собственный метод равенства (т. е. вы не можете сравнить свой собственный объект с нулевым значением, если вы сначала не поместили его в объект и все готово). больше проблем, связанных с нулем).
Я собирался написать об этом пост в блоге, но пока руки не дошли.Код немного недокументирован, но если он вам понравится, я могу его немного подчистить.
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 это означает, что вы можете делать с ним практически все, что захотите, в том числе использовать его в проприетарных решениях, не меняя при этом немного лицензирования.
Я расширил код Марка, сделав его полноценной реализацией 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);
}
}
К сожалению, вам придется написать метод для сравнения значений полей. System.ValueType
создан для использования отражения и сравнения значений полей struct
но даже это нецелесообразно из-за низкой производительности.Лучше всего переопределить Equals
метод, а также реализовать IEquatable<T>
интерфейс для строго типизированного Equals
перегрузка.
Пока вы этим занимаетесь, вы могли бы также предоставить хороший GetHashCode
переопределить, а также дополнить Equals
выполнение.Все эти шаги считаются хорошей практикой.
Для этого вам нужно будет использовать отражение, перейдите по этой ссылке --> Сравнение свойств объекта в С#