Вопрос или проблема
Используя VB.NET, я получаю список объектов. Из этого списка объектов мне нужно удалить дублирующиеся наборы данных, чтобы список был уникальным по всем столбцам. Читая на StackOverflow, общее мнение состоит в том, чтобы использовать GroupBy
, за которым следует Select
, однако каждый раз, когда я пытаюсь это сделать, возвращается вся коллекция.
В данном примере я ожидаю, что количество в uniqueData будет равно 3, но оно остается равным 4. dataSet1 и dataSet2 должны рассматриваться как дубликаты друг друга.
Module Program
Sub Main(args As String())
Dim data As New PersonCollection With {.PersonCollection = New List(Of PersonInfo)}
Dim dataSet1 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 10}
Dim dataSet2 As New PersonInfo With {.FirstName = "Джон", .LastName = "Херт", .Rating = 20}
Dim dataSet3 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 30}
Dim dataSet4 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 10}
data.PersonCollection.Add(dataSet1)
data.PersonCollection.Add(dataSet2)
data.PersonCollection.Add(dataSet3)
data.PersonCollection.Add(dataSet4)
Dim uniqueData = data.PersonCollection.GroupBy(Function(x) New With {x.FirstName, x.LastName, x.Rating}).Select(Function(x) x.First).ToList()
Console.ReadLine()
End Sub
Private Class PersonCollection
Property PersonCollection As List(Of PersonInfo)
End Class
Private Class PersonInfo
Property FirstName As String
Property LastName As String
Property Rating As Integer
End Class
End Module
Это работает с кортежами:
Module Program
Sub Main(args As String())
Dim data As New PersonCollection With {.PersonCollection = New List(Of PersonInfo)}
Dim dataSet1 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 10}
Dim dataSet2 As New PersonInfo With {.FirstName = "Джон", .LastName = "Херт", .Rating = 20}
Dim dataSet3 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 30}
Dim dataSet4 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 10}
data.PersonCollection.Add(dataSet1)
data.PersonCollection.Add(dataSet2)
data.PersonCollection.Add(dataSet3)
data.PersonCollection.Add(dataSet4)
Dim uniqueData = data.PersonCollection _
.GroupBy(Function(x) New Tuple(Of String, String, Integer)(
x.FirstName, x.LastName, x.Rating)) _
.Select(Function(x) x.First).ToList()
Console.ReadLine()
End Sub
Private Class PersonCollection
Property PersonCollection As List(Of PersonInfo)
End Class
Private Class PersonInfo
Property FirstName As String
Property LastName As String
Property Rating As Integer
End Class
End Module
Но лучше использовать Comparer
:
Module Program
Sub Main(args As String())
Dim data As New PersonCollection With {.PersonCollection = New List(Of PersonInfo)}
Dim dataSet1 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 10}
Dim dataSet2 As New PersonInfo With {.FirstName = "Джон", .LastName = "Херт", .Rating = 20}
Dim dataSet3 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 30}
Dim dataSet4 As New PersonInfo With {.FirstName = "Боб", .LastName = "Смит", .Rating = 10}
data.PersonCollection.Add(dataSet1)
data.PersonCollection.Add(dataSet2)
data.PersonCollection.Add(dataSet3)
data.PersonCollection.Add(dataSet4)
Dim comparer As New PersonInfoComparer
Dim uniqueData = data.PersonCollection.Distinct(comparer).ToList
Console.ReadLine()
End Sub
Private Class PersonCollection
Property PersonCollection As List(Of PersonInfo)
End Class
Private Class PersonInfo
Property FirstName As String
Property LastName As String
Property Rating As Integer
End Class
Private Class PersonInfoComparer : Inherits EqualityComparer(Of PersonInfo)
Public Overrides Function Equals(x As PersonInfo, y As PersonInfo) As Boolean
Return x.FirstName.Equals(y.FirstName) _
AndAlso x.LastName.Equals(y.LastName) _
AndAlso x.Rating.Equals(y.Rating)
End Function
Public Overrides Function GetHashCode(obj As PersonInfo) As Integer
#If NETSTANDARD2_0_OR_GREATER Or NETCOREAPP2_1_OR_GREATER Then
Return HashCode.Combine(obj.FirstName, obj.LastName, obj.Rating)
#Else
Return obj.FirstName.GetHashCode() _
Xor obj.LastName.GetHashCode() _
Xor obj.Rating.GetHashCode()
#End If
End Function
End Class
End Module
Попробуйте использовать кортеж вместо этого, поскольку он имеет семантику значений в отличие от типа анонимного класса, который имеет семантику ссылок. Т.е., два объекта сравниваются по ссылке, а не по их полям или свойствам.
.GroupBy(Function(x) (x.FirstName, x.LastName, x.Rating))
Обратите внимание, что синтаксис New With {x.FirstName ... }
работает с О/R маппером, потому что затем это преобразуется в SQL, и различие между семантикой значений и семантикой ссылок больше не применяется.
Другим решением является объявление свойств анонимного типа как Ключевых свойств.
.GroupBy(Function(x) New With {Key x.FirstName, Key x.LastName, Key x.Rating})
Но кортежи гораздо проще.
Мы можем немного упростить запрос, используя DistinctBy
вместо GroupBy
, за которым следует Select
:
Dim uniqueData = data.PersonCollection _
.DistinctBy(Function(x) (x.FirstName, x.LastName, x.Rating)) _
.ToList()
Ответ или решение
Решение проблемы с дубликатами в VB.NET
Введение
В VB.NET вы можете столкнуться с ситуацией, когда необходимо удалить дубликаты из списка объектов. В вашем случае, создание уникального списка объектов PersonInfo
на основе их свойств FirstName
, LastName
и Rating
может показаться сложной задачей. Однако правильное понимание концепций группировки и уникальности объектов поможет вам быстро обрести нужное решение. Давайте разберём подробнее, как это сделать.
Проблема с группировкой
В вашем текущем коде вы используете метод GroupBy
для создания групп объектов и последующего выбора уникальных значений. Однако, когда используется анонимный тип в этой форме:
.GroupBy(Function(x) New With {x.FirstName, x.LastName, x.Rating})
Сравнение объектов происходит по ссылке, а не по значению. Поэтому ваш уникальный набор остаётся пустым, если только объекты не являются ссылками на одну и ту же память.
Решение с использованием кортежей
Одним из самых простых и эффективных способов является использование кортежей. Кортежи в VB.NET имеют семантику значений, что означает, что при сравнении они будут сравниваться по его компонентам. Вам нужно заменить сборку группировки на следующий код:
Dim uniqueData = data.PersonCollection _
.GroupBy(Function(x) (x.FirstName, x.LastName, x.Rating)) _
.Select(Function(x) x.First).ToList()
Это позволит вам получить уникальный список объектов.
Решение через DistinctBy
Если вы используете .NET 6 или выше, вы можете упростить свой код, используя метод DistinctBy
, который позволяет легко извлекать уникальные объекты. Это значительно упрощает код:
Dim uniqueData = data.PersonCollection _
.DistinctBy(Function(x) (x.FirstName, x.LastName, x.Rating)) _
.ToList()
Этот метод предлагает более простую и интуитивно понятную реализацию для удаления дубликатов.
Использование сравнения через класс Comparer
Ещё один подход к решению данной задачи – заданный вами кастомный компаратор. Это необходимо, если вы используете коллекции, где предоставленные алгоритмы уже не работают должным образом. Вам нужно будет создать класс, производный от EqualityComparer(Of PersonInfo)
:
Private Class PersonInfoComparer : Inherits EqualityComparer(Of PersonInfo)
Public Overrides Function Equals(x As PersonInfo, y As PersonInfo) As Boolean
Return x.FirstName.Equals(y.FirstName) _
AndAlso x.LastName.Equals(y.LastName) _
AndAlso x.Rating.Equals(y.Rating)
End Function
Public Overrides Function GetHashCode(obj As PersonInfo) As Integer
Return HashCode.Combine(obj.FirstName, obj.LastName, obj.Rating)
End Function
End Class
Для использования компаратора в вашем коде:
Dim comparer As New PersonInfoComparer
Dim uniqueData = data.PersonCollection.Distinct(comparer).ToList()
Этот способ также позволяет гибко управлять логикой сравнения.
Заключение
При работе с дубликатами в коллекциях объектов VB.NET, вопросы семантики значений и ссылок могут стать серьезным препятствием. Используйте кортежи или методы DistinctBy
для удобства, или напишите свой собственный класс сравнения, если требуется более сложное поведение. Каждый из этих подходов позволит вам получить желаемый результат – уникальный список объектов PersonInfo
.
Если у вас остались вопросы или необходима дополнительная помощь, не стесняйтесь обращаться!