简体   繁体   English

等于 Override 和 LINQ GroupBy

[英]Equals Override and LINQ GroupBy

I have two classes, one called ArcPrimitive and the other called CirclePrimitive.我有两个类,一个叫做 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();
        }
    }

Now, elsewhere in my project I have collections (List<>) of ArcPrimitive objects and CirclePrimitive objects.现在,在我的项目的其他地方,我有 ArcPrimitive 对象和 CirclePrimitive 对象的 collections (List<>)。 I would like to group together ArcPrimitive objects and CirclePrimitive objects that are considered equal when my override Equals methods are used for each class.当我的覆盖 Equals 方法用于每个 class 时,我想将被认为相等的 ArcPrimitive 对象和 CirclePrimitive 对象组合在一起。 I have attempted to do this using the GroupBy() extension method on my collections of ArcPrimitive and CirclePrimitive objects.我试图在 ArcPrimitive 和 CirclePrimitive 对象的 collections 上使用 GroupBy() 扩展方法来执行此操作。 However, the only way that I could get the GroupBy method to correctly group the objects is if I used the following overload and supplied a class that implemented the IEqualityComparer interface:但是,如果我使用以下重载并提供实现 IEqualityComparer 接口的 class ,我可以获得 GroupBy 方法正确分组对象的唯一方法:

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();

My issue is that the EqualityComparer classes that I had to write are incredibly not-DRY (Don't Repeat Yourself), making me wonder if there is a better way.我的问题是我必须编写的 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.");
        }
    }

I have pursued a couple different ways of fixing this serious repetition.我已经采用了几种不同的方法来解决这种严重的重复问题。 One was to create a generic EqualityComparer class.一种是创建一个通用的 EqualityComparer class。 The issue I faced there was that I could not access the correct Equals override when the object type is not specified in the generic class.我遇到的问题是,当通用 class 中未指定 object 类型时,我无法访问正确的 Equals 覆盖。 The other method I tried was to override not only Equals but GetHashCode in my ArcPrimitive and CirclePrimitive classes in the hopes that GroupBy would just use the override methods to do the grouping.我尝试的另一种方法是在我的 ArcPrimitive 和 CirclePrimitive 类中不仅覆盖 Equals,而且覆盖 GetHashCode,希望 GroupBy 只使用覆盖方法进行分组。 However, I could not figure out how to correctly generate a hash code for these objects, because objects that return true from Equals have to have the same hash code, and I could not figure out how to apply my custom equality methods to the hash code function to get the necessary hash codes.但是,我无法弄清楚如何为这些对象正确生成 hash 代码,因为从 Equals 返回 true 的对象必须具有相同的 hash 代码,而且我无法弄清楚如何将我的自定义相等方法应用于 Z08040FC5778394434533 function 获取必要的 hash 代码。

Sorry for the long post, I just felt it was necessary to add the code to give the details of my issue.很抱歉发了这么长的帖子,我只是觉得有必要添加代码来提供我的问题的详细信息。

EDIT: response to NetMage comment This is a test to try to use IEquatable to control the equality comparison that GroupBy does for grouping and keys.编辑:对 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;
    }
}

As you can see, each object becomes a key and none are grouped together, even though I wanted objects with the same Value property to be grouped together.如您所见,每个 object 都成为一个键,并且没有一个组合在一起,即使我希望将具有相同Value属性的对象组合在一起。

Properly working version of IEquatable<T> example: 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM