继我的问题here之后,我正在尝试创建一个通用的值相等比较器。我之前从未玩过反射,所以不确定我是否在正确的轨道上,但无论如何我到目前为止都有这个想法:
bool ContainSameValues<T>(T t1, T t2)
{
if (t1 is ValueType || t1 is string)
{
return t1.Equals(t2);
}
else
{
IEnumerable<PropertyInfo> properties = t1.GetType().GetProperties().Where(p => p.CanRead);
foreach (var property in properties)
{
var p1 = property.GetValue(t1, null);
var p2 = property.GetValue(t2, null);
if( !ContainSameValues<p1.GetType()>(p1, p2) )
return false;
}
}
return true;
}
这不能编译,因为我无法弄清楚如何在递归调用中设置T的类型。是否可以动态执行此操作?
这里有几个相关的问题,我已经阅读了但是我无法完全了解它们如何适用于我的情况。
答案 0 :(得分:6)
如果您乐意根据静态知道的属性类型进行比较,则可以避免对调用进行反思。
这依赖于3.5中的表达式以简单的方式进行一次性反射,可以更好地做到这一点,以减少极端嵌套类型的工作量,但这对于大多数需求应该没问题。
如果你必须解决运行时类型,则需要一定程度的反射(尽管如果你再次缓存每个属性的访问和比较方法,这会很便宜),但由于子属性的运行时类型,这本身就要复杂得多可能不匹配,为了完全普遍,您将不得不考虑以下规则:
EqualityComparer<T>.Default
实现并且不再进一步递归
还有其他各种选择,但这应该让人深思,为什么完整的运行时分析很困难。
(请注意,我已经改变了你''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
class StaticPropertyTypeRecursiveEquality<T>
{
private static readonly Func<T,T, bool> actualEquals;
static StaticPropertyTypeRecursiveEquality()
{
if (typeof(IEquatable<T>).IsAssignableFrom(typeof(T)) ||
typeof(T).IsValueType ||
typeof(T).Equals(typeof(object)))
{
actualEquals =
(t1,t2) => EqualityComparer<T>.Default.Equals(t1, t2);
}
else
{
List<Func<T,T,bool>> recursionList = new List<Func<T,T,bool>>();
var getterGeneric =
typeof(StaticPropertyTypeRecursiveEquality<T>)
.GetMethod("MakePropertyGetter",
BindingFlags.NonPublic | BindingFlags.Static);
IEnumerable<PropertyInfo> properties = typeof(T)
.GetProperties()
.Where(p => p.CanRead);
foreach (var property in properties)
{
var specific = getterGeneric
.MakeGenericMethod(property.PropertyType);
var parameter = Expression.Parameter(typeof(T), "t");
var getterExpression = Expression.Lambda(
Expression.MakeMemberAccess(parameter, property),
parameter);
recursionList.Add((Func<T,T,bool>)specific.Invoke(
null,
new object[] { getterExpression }));
}
actualEquals = (t1,t2) =>
{
foreach (var p in recursionList)
{
if (t1 == null && t2 == null)
return true;
if (t1 == null || t2 == null)
return false;
if (!p(t1,t2))
return false;
}
return true;
};
}
}
private static Func<T,T,bool> MakePropertyGetter<TProperty>(
Expression<Func<T,TProperty>> getValueExpression)
{
var getValue = getValueExpression.Compile();
return (t1,t2) =>
{
return StaticPropertyTypeRecursiveEquality<TProperty>
.Equals(getValue(t1), getValue(t2));
};
}
public static bool Equals(T t1, T t2)
{
return actualEquals(t1,t2);
}
}
进行测试我使用了以下内容:
public class Foo
{
public int A { get; set; }
public int B { get; set; }
}
public class Loop
{
public int A { get; set; }
public Loop B { get; set; }
}
public class Test
{
static void Main(string[] args)
{
Console.WriteLine(StaticPropertyTypeRecursiveEquality<String>.Equals(
"foo", "bar"));
Console.WriteLine(StaticPropertyTypeRecursiveEquality<Foo>.Equals(
new Foo() { A = 1, B = 2 },
new Foo() { A = 1, B = 2 }));
Console.WriteLine(StaticPropertyTypeRecursiveEquality<Loop>.Equals(
new Loop() { A = 1, B = new Loop() { A = 3 } },
new Loop() { A = 1, B = new Loop() { A = 3 } }));
Console.ReadLine();
}
}
答案 1 :(得分:4)
您需要使用反射来调用方法,如下所示:
MethodInfo genericMethod = typeof(SomeClass).GetMethod("ContainSameValues");
MethodInfo specificMethod = genericMethod.MakeGenericMethod(p1.GetType());
if (!(bool)specificMethod.Invoke(this, new object[] { p1, p2 }))
但是,您的方法首先不应该是通用的;它应该只需要两个object
参数。 (或者,如果它是通用的,它应该以泛型类型缓存属性和委托)