我有两个类,一个称为 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);
}