开发者

How .NET determines equal objects in LINQ "select new"?

开发者 https://www.devze.com 2023-03-23 21:50 出处:网络
Here is example: class 开发者_如何学CA { public long ParentId; public string ParentName; public string Name;

Here is example:

class 开发者_如何学CA
{ 
    public long ParentId; 
    public string ParentName; 
    public string Name; 
}

// list is IEnumerable<A>
var selected = list
    .Select(x => new {
        parent = new {
            id = x.ParentId,
            name = x.ParentName
        }
        name = x.Name
    });
var grouped = selected.GroupBy(x => x.parent);

So, as grouping completed successfully, I make conclusion, that parent is not created for two different entities if both of them have the same ParentId and ParentName. By other words, if list[i].ParentId == list[j].ParentId and list[i].ParentName == list[j].ParentName, then after selecting selected[i].parent == selected[j].parent.

Am I right? I thought, that new creates new object on every iteration through source collection. How .NET does this?


It's a matter of how Equals is impemented. For anonymous classes it's returns true if and only if the properties match and for each of the properties there is equality.

Compare these two:

Console.WriteLine(new { A = 1, B = "a" } == new { A = 1, B = "a" });  //false
Console.WriteLine(new { A = 1, B = "a" }.Equals(new { A = 1, B = "a" }));  //true


GroupBy uses a standard hash+equals approach (common with Hashtable, Dictionary<,>, etc), which is to say:

  • GetHashCode defines definite non-equality (when different) and possible equality (when same)
  • Equals (IEquatable<T>.Equals or object.Equals) defines equality

Your group by projection compares ParentName, so that is used when comparing items. Since string has well defined GetHashCode/Equals, this works fine.

Re your final question:

I thought, that new creates new object on every iteration through source collection.

Strictly, yes. But it only enumerates it once, so that isn't an issue. Even if it didn't, the anonymous type new {...} itself has an equality definition based on the component members.


Each .Net object implements the IComparer interface and has it's own implementation of the CompareTo method. .Net just uses this method to determine whether something is equal, in this case .net is just checking that both objects public properties have the same values therefore they are equal.

EDIT: Sorry I was confusing IComparer CompareTo with object.Equals, each object implements the Equals method and as an example String class overrides this method and just checks that both strings contain the same value not reference the same memory address.


Well to override the defualt comparison behaviour of .Net you should do something like this:

class A
{ 
    public long ParentId; 
    public string ParentName; 
    public string Name; 


    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj.GetType() != typeof(A))
            return false;
        var other=(A) obj;
        return Equals(other.ParentId, ParentId) && Equals(other.ParentName, ParentName);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (ParentName.GetHashCode() * 397) ^ ParentId.GetHashCode();
        }
    }
}


It doesn't call == operator.

It asks EqualityComparer<T>.Default if instances are equal. In turn, it calls Equals from IEquatable implementation, if there is any, or Object.Equals method as the last resort.

The default implementation of Equals supports reference equality for reference types, and bitwise equality for value types.

However you are free to override the method if you need to set up your own rules for equality.

In your example, parent if of an anonymous type. C# compiler generates field-by-field Equals and GetHashCode implementations because obviously you can't provide them yourself.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号