I have the following
Dim query = From city In myCountry.Cities
From street In city.Streets
Sel开发者_运维百科ect New StreetInfo With {.Name = street.Name, .Index = street.Index}
Distinct
Now. I remarqued that if I have multiple identical streets (having the same Name
and Index
), the StreetInfo list contains all that duplicates...
How should I specify the really distinct values for the resulting collection of StreetInfo values?
Say, the StreetInfo
class is defined like this:
Public Class StreetInfo
Public Property Name As String
Public Property Index As Integer
End Class
Distinct
uses the default equality comparer, which means you'll have to override Equals
and GetHashCode
on StreetInfo
for it to work.
You could override GetHashCode
and Equals
in StreetInfo
- but that wouldn't be terribly nice in its existing form given that it's mutable. (You could create two instances which are equal one minute and then not equal the next. That often makes for a confusing debugging experience.) Alternatively, you could use an anonymous type, making sure you use readonly "key" properties:
Dim query = From city In myCountry.Cities
From street In city.Streets
Select New With { Key .Name = street.Name, Key .Index = street.Index}
Distinct
Here the equality and hash code will be provided automatically by the compiler. This is then equivalent to:
Dim query = From city In myCountry.Cities
From street In city.Streets
Select street.Name, street.Index
Distinct
See the MSDN article on anonymous types for more information.
If you definitely need StreetInfo
values afterwards and you don't want to make it immutable, you could add another projection (Select) afterwards. I don't know if it's possible to append another Select clause after a Distinct clause in a single query, but you could use a second expression:
Dim query = From city In myCountry.Cities
From street In city.Streets
Select street.Name, street.Index
Distinct
Dim query2 = From street in query
Select New StreetInfo With {.Name = street.Name, _
.Index = street.Index}
As others have mentioned, using anonymous types is a good solution because the compiler creates the Equals
and GetHashCode
methods for you. However, the problem is that you lose the references to your original objects (StreetInfo
) and instead you get a list of distinct anonymous types.
Instead, you can use a custom IEqualityComparer
to compare the original objects based on any custom field you want. Below is a reusable CustomComparer
that compares any object based on any custom criteria.
Public Class CustomComparer(Of TSource As Class, TCompareType)
Implements IEqualityComparer(Of TSource)
Private getComparisonObject As Func(Of TSource, TCompareType)
Public Sub New(ByVal getComparisonObject As Func(Of TSource, TCompareType))
If (getComparisonObject Is Nothing) Then
Throw New ArgumentNullException("getComparisonObject")
End If
Me.getComparisonObject = getComparisonObject
End Sub
Public Function Equals(ByVal x As TSource, ByVal y As TSource) As Boolean
If (x Is Nothing) Then
Return (y Is Nothing)
ElseIf (y Is Nothing) Then
Return false
End If
Return EqualityComparer.Default.Equals(getComparisonObject(x), getComparisonObject(y))
End Function
Public Function GetHashCode(ByVal obj As TSource) As Integer
Return EqualityComparer.Default.GetHashCode(getComparisonObject(obj))
End Function
End Class
This allows you to write the following code:
Dim query = From city In myCountry.Cities
From street In city.Streets
Select New StreetInfo With {.Name = street.Name, .Index = street.Index}
query = query.Distinct(new CustomComparer(function (street) New With {Key.Name = street.Name, Key.Index = street.Index}))
This will return all distinct StreetInfo
instead of returning distinct anonymous types.
(Note: Please excuse any VB.NET syntax issues ... I switched to C# before lambdas and anon types)
精彩评论