C# で 2 つのデータ転送オブジェクトが等しいプロパティを持っているかどうかをすばやく確認するにはどうすればよいですか?
質問
これらのデータ転送オブジェクトがあります。
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 つの論理式を必要としないもの) はありますか?
構造体への切り替えはオプションではありません。
解決
どのようにいくつかの反射、おそらくパフォーマンスのためExpression.Compile()
を使用してはどうですか? (ここでは静的ctorのは、我々は唯一の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)
私のものをチェックしてください MemberwiseEqualityComparer それがあなたのニーズに合うかどうかを確認してください。
とても使いやすく、非常に効率的です。IL-emit を使用して、最初の実行時に Equals 関数と GetHashCode 関数全体を生成します (使用する型ごとに 1 回)。これは、その型のデフォルトの等価比較子 (EqualityComparer.Default) を使用して、指定されたオブジェクトの各フィールド (プライベートまたはパブリック) を比較します。しばらく本番環境で使用しており、安定しているようですが、保証はしません =)
これは、独自のequalsメソッドをロールするときにめったに考えられない厄介なエッジケースをすべて処理します(つまり、最初にオブジェクトをボックス化してロットをオフにしない限り、独自のオブジェクトを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ライセンス つまり、ライセンスを少し変更することなく、独自のソリューションでの使用を含め、これを使用してほとんど何でもできるということです。
私は私自身の使用のための本格的なされた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
のフィールド値をリフレクションを使用して比較するために構築されていますが、でもこれは、パフォーマンスの低下にunadvisableです。行うための最善のことは、Equals
メソッドをオーバーライドしても、 IEquatable<T>
<を実装することです/ >強く型付けされたEquals
過負荷のためのインタフェースます。
、あなたにもGetHashCode
実装を補完するためにも良いEquals
オーバーライドを提供することがあります。これらの工程の全ては、良い習慣と見なされます。
あなたはこれを行うためにリフレクションを使用する必要があります、このリンクをクリックしてくださいC#
ののプロパティ