How .NET determines equal objects in LINQ "select new"?
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
.
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
orobject.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.
精彩评论