问题描述
我有两个类,一个叫做 ArcPrimitive,另一个叫做 CirclePrimitive。
public class ArcPrimitive : Primitive
{
public double Angle { get; set; }
public double Length { get; set; }
public override bool Equals(object obj)
{
if (obj is ArcPrimitive other)
{
return EqualsHelpers.EqualAngles(Angle,other.Angle) && EqualsHelpers.EqualLengths(Length,other.Length);
}
else if (obj is ArcPrimitiveType otherType)
{
return EqualsHelpers.EqualAngles(Angle,otherType.Angle) && EqualsHelpers.EqualLengths(Length,otherType.Length);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
public class CirclePrimitive : Primitive
{
public double Radius { get; set; }
public override bool Equals(object obj)
{
if (obj is CirclePrimitive other)
{
return EqualsHelpers.EqualLengths(Radius,other.Radius);
}
else if (obj is CirclePrimitiveType otherType)
{
return EqualsHelpers.EqualLengths(Radius,otherType.Radius);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
现在,在我的项目的其他地方,我有 ArcPrimitive 对象和 CirclePrimitive 对象的集合 (List)。我想将 ArcPrimitive 对象和 CirclePrimitive 对象组合在一起,当我的覆盖 Equals 方法用于每个类时,它们被认为是相等的。我尝试在我的 ArcPrimitive 和 CirclePrimitive 对象集合上使用 GroupBy() 扩展方法来执行此操作。但是,我可以让 GroupBy 方法正确地对对象进行分组的唯一方法是使用以下重载并提供一个实现 IEqualityComparer 接口的类:
GroupBy<TSource,TKey>(IEnumerable<TSource>,Func<TSource,TKey>,IEqualityComparer<TKey>)
ArcPrimitiveEqualityComparer apec = new ArcPrimitiveEqualityComparer(arcPrimitives);
CirclePrimitiveEqualityComparer cpec = new CirclePrimitiveEqualityComparer(circlePrimitives);
var arcPrimitiveGroups = arcPrimitives.GroupBy(p => p,apec).ToList();
var circlePrimitiveGroups = circlePrimitives.GroupBy(p => p,cpec).ToList();
我的问题是我必须编写的 EqualityComparer 类非常不干(不要重复自己),这让我想知道是否有更好的方法。
public class ArcPrimitiveEqualityComparer : IEqualityComparer<ArcPrimitive>
{
public Dictionary<ArcPrimitive,int> ArcHashDict { get; set; }
public ArcPrimitiveEqualityComparer(List<ArcPrimitive> arcPrimitives)
{
ArcHashDict = new Dictionary<ArcPrimitive,int>();
int hCode = 0;
foreach (ArcPrimitive arcPrimitive in arcPrimitives)
{
var keys = ArcHashDict.Keys;
bool matchFound = false;
foreach (var key in keys)
{
if (arcPrimitive.Equals(key))
{
matchFound = true;
}
}
if (matchFound == false)
{
ArcHashDict.Add(arcPrimitive,hCode);
hCode += 1;
}
}
}
public bool Equals(ArcPrimitive ap1,ArcPrimitive ap2)
{
return ap1.Equals(ap2);
}
public int GetHashCode(ArcPrimitive ap)
{
foreach (var key in ArcHashDict.Keys)
{
if (ap.Equals(key))
{
return ArcHashDict[key];
}
}
throw new Exception("ArcPrimitive does not have a hash code.");
}
}
public class CirclePrimitiveEqualityComparer : IEqualityComparer<CirclePrimitive>
{
public Dictionary<CirclePrimitive,int> CircleHashDict { get; set; }
public CirclePrimitiveEqualityComparer(List<CirclePrimitive> circlePrimitives)
{
CircleHashDict = new Dictionary<CirclePrimitive,int>();
int hCode = 0;
foreach (CirclePrimitive circlePrimitive in circlePrimitives)
{
var keys = CircleHashDict.Keys;
bool matchFound = false;
foreach (var key in keys)
{
if (circlePrimitive.Equals(key))
{
matchFound = true;
}
}
if (matchFound == false)
{
CircleHashDict.Add(circlePrimitive,hCode);
hCode += 1;
}
}
}
public bool Equals(CirclePrimitive cp1,CirclePrimitive cp2)
{
return cp1.Equals(cp2);
}
public int GetHashCode(CirclePrimitive cp)
{
foreach (var key in CircleHashDict.Keys)
{
if (cp.Equals(key))
{
return CircleHashDict[key];
}
}
throw new Exception("CirclePrimitive does not have a hash code.");
}
}
我采用了几种不同的方法来解决这个严重的重复问题。一种是创建一个通用的 EqualityComparer 类。我在那里遇到的问题是,当通用类中未指定对象类型时,我无法访问正确的 Equals 覆盖。我尝试的另一种方法是在 ArcPrimitive 和 CirclePrimitive 类中不仅覆盖 Equals 还覆盖 GetHashCode,希望 GroupBy 只使用覆盖方法进行分组。但是,我无法弄清楚如何为这些对象正确生成哈希码,因为从 Equals 返回 true 的对象必须具有相同的哈希码,而且我无法弄清楚如何将我的自定义相等方法应用于哈希码函数来获取必要的哈希码。
抱歉,帖子太长了,我只是觉得有必要添加代码以提供我的问题的详细信息。
编辑:对 NetMage 评论的回应 这是一个尝试使用 IEquatable 来控制 GroupBy 对分组和键所做的相等比较的测试。
class Program
{
static void Main(string[] args)
{
List<Test> testList = new List<Test>
{
new Test(1),new Test(1),new Test(2),new Test(3),new Test(3)
};
var result = testList.GroupBy(p => p);
foreach (var group in result)
{
Console.WriteLine($"Key value: {group.Key.Value}; Group count {group.Count()}");
}
Console.ReadLine();
// Output
// Key value: 1; Group count: 1
// Key value: 1; Group count: 1
// Key value: 2; Group count: 1
// Key value: 3; Group count: 1
// Key value: 3; Group count: 1
}
}
class Test : IEquatable<Test>
{
public int Value { get; set; }
public Test(int val)
{
Value = val;
}
public bool Equals(Test other)
{
return Value == other.Value;
}
}
如您所见,每个对象都变成了一个键,并且没有一个被组合在一起,即使我希望将具有相同 Value
属性的对象组合在一起。
解决方法
IEquatable<T>
示例的正常工作版本:
void Main() {
var testList = new[] { 1,1,2,3,3 }.Select(n => new Test(n)).ToList();
var result = testList.GroupBy(p => p);
foreach (var group in result)
Console.WriteLine($"Key value: {group.Key.Value}; Group count {group.Count()}");
// Output
//Key value: 1; Group count 2
//Key value: 2; Group count 1
//Key value: 3; Group count 2
}
class Test : IEquatable<Test> {
public int Value { get; set; }
public Test(int val) => Value = val;
public override bool Equals(object obj) => obj is Test otherTest && this.Equals(otherTest);
public bool Equals(Test other) => other is not null && Value == other.Value;
public override int GetHashCode() => Value;
public static bool operator==(Test aTest,Test bTest) => aTest is not null && aTest.Equals(bTest);
public static bool operator!=(Test aTest,Test bTest) => !(aTest == bTest);
}