动态设置generics类型参数

继我的问题之后,我正在尝试创建一个通用的值相等比较器。 我以前从未玩过reflection,所以不确定我是否在正确的轨道上,但无论如何我到目前为止都有这个想法:

bool ContainSameValues(T t1, T t2) { if (t1 is ValueType || t1 is string) { return t1.Equals(t2); } else { IEnumerable 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, p2) ) return false; } } return true; } 

这不能编译,因为我无法弄清楚如何在递归调用中设置T的类型。 是否可以动态执行此操作?

这里有几个相关的问题,我已经阅读了但是我无法完全了解它们如何适用于我的情况。

如果您乐意根据静态知道的属性类型进行比较,则可以避免反映调用。

这依赖于3.5中的表达式以简单的方式进行一次性reflection,可以更好地做到这一点,以减少极端嵌套类型的工作量,但这应该适合大多数需求。

如果必须解决运行时类型,则需要一定程度的reflection(尽管如果再次缓存每个属性访问和比较方法,这将是便宜的)但这本身就要复杂得多,因为子属性上的运行时类型可能不匹配因此,为了完全普遍,您必须考虑以下规则:

  • 认为不匹配的类型不相等
    • 简单易懂,易于实施
    • 不太可能是一个有用的操作
  • 在类型分歧的点上使用标准EqualityComparer.Default实现两者并且不再进一步递归
    • 实施起来更简单,更难实施。
  • 如果它们具有本身相同的共同属性子集,则认为是相等的
    • 复杂,不是非常有意义
  • 如果它们共享相同的属性子集(基于名称和类型),则认为它们是相等的
    • 复杂,进入鸭子打字

还有其他各种选择,但这应该是思考为什么完整的运行时分析很难。

(请注意,我已经改变了你”叶子’终止守卫是我认为优越的,如果你想因某种原因只是使用刺痛/价值类型而感到自由)

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Linq.Expressions; class StaticPropertyTypeRecursiveEquality { private static readonly Func actualEquals; static StaticPropertyTypeRecursiveEquality() { if (typeof(IEquatable).IsAssignableFrom(typeof(T)) || typeof(T).IsValueType || typeof(T).Equals(typeof(object))) { actualEquals = (t1,t2) => EqualityComparer.Default.Equals(t1, t2); } else { List> recursionList = new List>(); var getterGeneric = typeof(StaticPropertyTypeRecursiveEquality) .GetMethod("MakePropertyGetter", BindingFlags.NonPublic | BindingFlags.Static); IEnumerable 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)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 MakePropertyGetter( Expression> getValueExpression) { var getValue = getValueExpression.Compile(); return (t1,t2) => { return StaticPropertyTypeRecursiveEquality .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.Equals( "foo", "bar")); Console.WriteLine(StaticPropertyTypeRecursiveEquality.Equals( new Foo() { A = 1, B = 2 }, new Foo() { A = 1, B = 2 })); Console.WriteLine(StaticPropertyTypeRecursiveEquality.Equals( new Loop() { A = 1, B = new Loop() { A = 3 } }, new Loop() { A = 1, B = new Loop() { A = 3 } })); Console.ReadLine(); } } 

你需要使用reflection来调用方法,如下所示:

 MethodInfo genericMethod = typeof(SomeClass).GetMethod("ContainSameValues"); MethodInfo specificMethod = genericMethod.MakeGenericMethod(p1.GetType()); if (!(bool)specificMethod.Invoke(this, new object[] { p1, p2 })) 

但是,您的方法首先不应该是通用的; 它应该只需要两个object参数。 (或者,如果它是通用的,它应该以generics类型缓存属性和委托)