Trong khi tạo khung thử nghiệm của tôi, tôi đã tìm thấy một vấn đề lạ.So sánh PropertyInfo từ Type.GetProperties() và biểu thức lambda
Tôi muốn tạo một lớp tĩnh cho phép tôi so sánh các đối tượng cùng loại với thuộc tính của chúng, nhưng với khả năng bỏ qua một số trong số chúng.
Tôi muốn có API thông thạo đơn giản cho điều này, do đó, một cuộc gọi như TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second);
sẽ trả về true nếu các đối tượng đã cho bằng nhau trên mọi thuộc tính ngoại trừ Id
và Name
(chúng sẽ không được kiểm tra bình đẳng).
Đây là mã của tôi. Tất nhiên đó là một ví dụ tầm thường (với một số rõ ràng quá tải các phương thức bị thiếu), nhưng tôi muốn trích xuất mã đơn giản nhất có thể. Kịch bản trường hợp thực sự phức tạp hơn một chút, vì vậy tôi không thực sự muốn thay đổi cách tiếp cận này.
Phương pháp FindProperty
gần như là bản sao dán từ AutoMapper library.
Object wrapper cho API thạo:
public class TestEqualityHelper<T>
{
public List<PropertyInfo> IgnoredProps = new List<PropertyInfo>();
public T Value;
}
thứ thạo:
public static class FluentExtension
{
//Extension method to speak fluently. It finds the property mentioned
// in 'ignore' parameter and adds it to the list.
public static TestEqualityHelper<T> Ignore<T>(this T value,
Expression<Func<T, object>> ignore)
{
var eh = new TestEqualityHelper<T> { Value = value };
//Mind the magic here!
var member = FindProperty(ignore);
eh.IgnoredProps.Add((PropertyInfo)member);
return eh;
}
//Extract the MemberInfo from the given lambda
private static MemberInfo FindProperty(LambdaExpression lambdaExpression)
{
Expression expressionToCheck = lambdaExpression;
var done = false;
while (!done)
{
switch (expressionToCheck.NodeType)
{
case ExpressionType.Convert:
expressionToCheck
= ((UnaryExpression)expressionToCheck).Operand;
break;
case ExpressionType.Lambda:
expressionToCheck
= ((LambdaExpression)expressionToCheck).Body;
break;
case ExpressionType.MemberAccess:
var memberExpression
= (MemberExpression)expressionToCheck;
if (memberExpression.Expression.NodeType
!= ExpressionType.Parameter &&
memberExpression.Expression.NodeType
!= ExpressionType.Convert)
{
throw new Exception("Something went wrong");
}
return memberExpression.Member;
default:
done = true;
break;
}
}
throw new Exception("Something went wrong");
}
}
Các Comparer thực tế:
public static class TestEqualityComparer
{
public static bool MyEquals<T>(TestEqualityHelper<T> a, T b)
{
return DoMyEquals(a.Value, b, a.IgnoredProps);
}
private static bool DoMyEquals<T>(T a, T b,
IEnumerable<PropertyInfo> ignoredProperties)
{
var t = typeof(T);
IEnumerable<PropertyInfo> props;
if (ignoredProperties != null && ignoredProperties.Any())
{
//THE PROBLEM IS HERE!
props =
t.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Except(ignoredProperties);
}
else
{
props =
t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
}
return props.All(f => f.GetValue(a, null).Equals(f.GetValue(b, null)));
}
}
Đó là về cơ bản nó.
Và đây là hai đoạn thử nghiệm, người đầu tiên làm việc, điều thứ hai thất bại:
//These are the simple objects we'll compare
public class Base
{
public decimal Id { get; set; }
public string Name { get; set; }
}
public class Derived : Base
{ }
[TestMethod]
public void ListUsers()
{
//TRUE
var f = new Base { Id = 5, Name = "asdas" };
var s = new Base { Id = 6, Name = "asdas" };
Assert.IsTrue(TestEqualityComparer.MyEquals(f.Ignore(x => x.Id), s));
//FALSE
var f2 = new Derived { Id = 5, Name = "asdas" };
var s2 = new Derived { Id = 6, Name = "asdas" };
Assert.IsTrue(TestEqualityComparer.MyEquals(f2.Ignore(x => x.Id), s2));
}
Vấn đề là với Except
phương pháp trong DoMyEquals
.
Thuộc tính được trả về bởi FindProperty
không bằng với số được trả lại bởi Type.GetProperties
. Sự khác biệt tôi phát hiện là ở PropertyInfo.ReflectedType
.
không phân biệt các loại của các đối tượng của tôi,
FindProperty
nói với tôi rằng loại phản ánh làBase
.thuộc tính được trả lại bởi
Type.GetProperties
cóReflectedType
được đặt thànhBase
hoặcDerived
, tùy thuộc vào loại đối tượng thực tế.
Tôi không biết cách giải quyết. Tôi có thể kiểm tra kiểu tham số trong lambda, nhưng trong bước tiếp theo tôi muốn cho phép các cấu trúc như Ignore(x=>x.Some.Deep.Property)
, vì vậy có thể nó sẽ không làm.
Bất kỳ đề xuất nào về cách so sánh PropertyInfo
hoặc cách truy xuất chúng từ lambdas đúng cách sẽ được đánh giá cao.
Bạn đã thử chơi với giá trị BindingFlags.FlattenHierarchy trong GetProperties chưa? Xem nó có thay đổi gì không? –
Không có may mắn ở đó, nhưng cảm ơn bạn đã gợi ý. Tôi nghĩ ** BindingFlags chỉ có thể thay đổi _which_ thành viên được trả về, nhưng chúng sẽ không ảnh hưởng đến các thuộc tính của chúng. Tôi tin rằng giải pháp sẽ có trong một số trò chơi với FindProperty. –
Có thể thêm một bước thứ hai, hacky vào FindProperty, sau khi bạn đã nhận GetProperty thành viên chạy trên loại (bạn cũng có thể nhận được thông qua biểu thức) với tên thành viên? Đó là một hack, nhưng nó có thể hoạt động. –