【发布时间】:2020-09-22 07:48:05
【问题描述】:
我有一个可以直接序列化的数据结构(例如,XML、JSON):有一个主类C1 和其他几个类C2、C3、...、Cn。所有Ci 类都有public 属性,它们是
- 原始类型或
string -
IEnumerable<T>其中T是原始类型或string或任何类Cj(其中j != i) -
Cj(其中j != i)
没有循环引用。
我想定义一个通用相等比较器 ValueEqualityComparer<T>,它比较任何类 Ci 值,即以以下规范方式:
- 如果
T是原始类型,则使用Equals(或==) - 如果
T类型为IEnumerable<S>,则在S对象上使用Enumerable.SequenceEqual,并将ValueEqualityComparer<S>作为第三个参数 - 否则,对于每个公共属性
P使用ValueEqualityComparer<P>.Equals并通过&&连接这些结果。
我已经了解到像上面这样的模式匹配是不可能直接进行的,所以我需要反思。但我正在为如何做到这一点而苦苦挣扎。
这是我目前所写的:
public class ValueEqualityComparer<T> : IEqualityComparer<T>
{
public static readonly ValueEqualityComparer<T> Get = new ValueEqualityComparer<T>();
private static readonly Type _type = typeof(T);
private static readonly bool _isPrimitiveOrString = IsPrimitiveOrString(_type);
private static readonly Type _enumerableElementType = GetEnumerableElementTypeOrNull(_type);
private static bool _isEnumerable => _enumerableElementType != null;
private ValueEqualityComparer() {}
public bool Equals(T x, T y)
{
if (x == null || y == null)
{
if (x == null && y == null)
return true;
return false;
}
if (_isPrimitive)
return x.Equals(y);
if (_isEnumerable)
{
var comparerType = typeof(ValueEqualityComparer<>).MakeGenericType(new Type[] { _enumerableElementType });
var elementComparer = comparerType.GetField("Get").GetValue(null);
// not sure about this line:
var result = Expression.Call(typeof(Enumerable), "SequenceEqual", new Type[] { _enumerableElementType },
new Expression[] { Expression.Constant(x), Expression.Constant(y), Expression.Constant(elementComparer) });
}
// TODO: iterate public properties, use corresponding ValueEqualityComparers
}
public int GetHashCode(T obj)
{
// TODO
}
private static bool IsPrimitiveOrString(Type t) => t.IsPrimitive || t == typeof(string);
// if we have e.g. IEnumerable<string>, it will return string
private static Type GetEnumerableElementTypeOrNull(Type t)
{
Type enumerableType = t.GetInterfaces().Where(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(IEnumerable<>)).FirstOrDefault();
return enumerableType?.GetGenericArguments().Single();
}
}
关于// not sure about this line: 线路的问题:
- 目标是调用
Enumerable.SequenceEqual<S>(x, y, ValueEqualityComparer<S>.Get),其中S是x和y的元素类型(即T是IEnumerable<S>)。我为此写的那一行是否正确? - 如何获得该调用的结果(即
true或false)?
请不要填写// TODO部分;我想尽我所能为自己弄清楚。
我不想比较序列化的对象,因为它对调试没有多大帮助。毕竟,比较器是测试所必需的。
【问题讨论】:
-
您所说的规则中没有任何内容阻止
Cx拥有Cy和Cy拥有Cx。这种情况(或更大的周期)实际上是否允许?如果是这样,您将如何防止无限回归? -
关于你所说的“所有类
Ci都有公共属性,它们是......”你的意思是不会有循环引用吗?从您当前编写的方式来看,循环引用仍然是可能的。C1可以有一个C2属性,C2可以有一个C1属性,两个对象可以互相引用,这可能会导致无限递归。 -
为什么不简单地同时序列化
x和y并比较序列化结果呢? -
我不认为
Expression.Call是正确的,IMO 你应该得到SequenceEqual的MethodInfo然后使用MethodInfo.Invoke -
return Equals(x, y)看起来有问题.. 可能是无限循环。如果我尝试这样做,我想我会使用重载方法、强制转换和审查多态行为.. 可能还有 Tuple.Create for GetHashCode 重载方法..
标签: c# generics reflection expression