开发者

What means Distinct in LINQ?

开发者 https://www.devze.com 2023-04-11 11:29 出处:网络
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}

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)

0

精彩评论

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