LINQ Виберіть відмінність з анонімними типами


150

Тож у мене є колекція предметів. Точний тип не важливий. З цього я хочу витягти всі унікальні пари пари певних властивостей, таким чином:

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

Отже, моє запитання: Чи буде в цьому випадку відмінністю використовувати об'єкт за замовчуванням рівний (що буде для мене марним, оскільки кожен об'єкт новий), чи можна сказати робити різні рівні (у цьому випадку рівні значення Альфи та Браво => рівні екземпляри)? Чи є якийсь спосіб досягти цього результату, якщо цього не зробити?


Це LINQ до об'єктів чи LINQ до SQL? Якщо тільки об’єкти, вам, мабуть, не пощастило. Однак, якщо L2S, він може працювати, оскільки DISTINCT буде передано на оператор SQL.
Джеймс Курран

Відповіді:


188

Прочитайте чудовий пост К. Скотта Аллена тут:

І рівність для всіх ... Анонімні типи

Коротка відповідь (і я цитую):

Виявляється компілятор C # замінює рівності та GetHashCode для анонімних типів. Реалізація двох перекритих методів використовує всі загальнодоступні властивості типу для обчислення хеш-коду об'єкта та перевірки рівності. Якщо два об’єкти одного анонімного типу мають усі однакові значення за своїми властивостями - об'єкти рівні.

Тому цілком безпечно використовувати метод Distinct () у запиті, який повертає анонімні типи.


2
Це справедливо, я думаю, якщо самі властивості є типовими типами або реалізують ціннісну рівність - дивіться мою відповідь.
tvanfosson

Так, оскільки він використовує GetHashCode для кожної властивості, то він би працював лише в тому випадку, якщо кожна власність мала б свою власну унікальну реалізацію. Я думаю, що у більшості випадків використання є лише прості типи як властивості, тому це, як правило, безпечно.
Метт Гамільтон

4
Це означає, що рівність двох анонімних типів залежить від рівності членів, що добре для мене, оскільки члени визначені десь, де я можу отримати і перекрити рівність, якщо мені доведеться. Мені просто не хотілося створювати для цього клас для того, щоб перекрити рівні.
GWLlosa

3
Можливо, варто звернутись із клопотанням про те, щоб MS вводила синтаксис "ключ" у C #, який має VB (де ви можете вказати певні властивості анонімного типу як "первинний ключ" - див. Допис у блозі, до якого я пов’язаний).
Метт Гамільтон

1
Дуже цікава стаття. Дякую!
Олександр Прокоф’єв

14
public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

Вибачте за попереднє форматування зіпсованого


Це розширення не може обробляти тип objectта object. Якщо і те, і інше object, stringце все-таки повертати повторювані рядки. Спробуйте FirstNameтип typeof objectі призначте stringтам же .
CallMeLaNN

Це чудова відповідь для набраних об’єктів, але не потрібна для анонімних типів.
crokusek

5

Цікаво, що він працює в C #, але не в VB

Повертає 26 літер:

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

Повернення 52 ...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()

11
Якщо ви додасте Keyключове слово до анонімного типу, воно .Distinct()буде працювати за призначенням (наприклад New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}).
Cᴏʀʏ

3
Кори правильно. Правильний переклад коду C # new {A = b}є New {Key .A = b}. Не ключові властивості в анонімних VB-класах змінні, тому їх порівнюють за посиланням. У C # всі властивості анонімних класів незмінні.
Хайнзі

4

Я провів невеликий тест і виявив, що якщо властивості є типовими типами, це, здається, працює нормально. Якщо вони не є типовими значеннями, то для цього типу потрібно забезпечити власні реалізації Equals та GetHashCode. Струни, я думаю, працювали б.


2

Ви можете створити власний метод розрізненого розширення, який займає лямбда-вираз. Ось приклад

Створіть клас, який походить від інтерфейсу IEqualityComparer

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

Потім створіть свій метод розрізненого розширення

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

і ви можете використовувати цей метод для пошуку різних предметів

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

Це розширення не може обробляти тип objectта object. Якщо і те, і інше object, stringце все-таки повертати повторювані рядки. Спробуйте FirstNameтип typeof objectі призначте stringтам же .
CallMeLaNN

0

Якщо Alphaі Bravoобидва успадковують із загального класу, ви зможете продиктувати перевірку рівності у батьківському класі, реалізуючи IEquatable<T>.

Наприклад:

public class CommonClass : IEquatable<CommonClass>
{
    // needed for Distinct()
    public override int GetHashCode() 
    {
        return base.GetHashCode();
    }

    public bool Equals(CommonClass other)
    {
        if (other == null) return false;
        return [equality test];
    }
}

тому якщо ви використовуєте як властивості класів анонімних типів, що реалізує IEquatable <T>, називається Equals замість поведінки за замовчуванням (перевірка всіх загальнодоступних властивостей за допомогою відображення?)
D_Guidi

0

Гей, там у мене така ж проблема, і я знайшов рішення. Ви повинні реалізувати інтерфейс IEquatable або просто перекрити (Equals & GetHashCode) методи. Але це не хитрість, трюк, що приходить в методі GetHashCode. Ви не повинні повертати хеш-код об'єкта вашого класу, але ви повинні повернути хеш властивості, яку ви хочете порівняти.

public override bool Equals(object obj)
    {
        Person p = obj as Person;
        if ( obj == null )
            return false;
        if ( object.ReferenceEquals( p , this ) )
            return true;
        if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
            return true;
        return false;
        //return base.Equals( obj );
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }

Як ви бачите, я отримав клас, який називається person, отримав 3 властивості (Name, Age, IsEgyptian "Тому що я є") У GetHashCode я повернув хеш властивості Name, а не "Person".

Спробуйте, і це спрацює ISA. Дякую, Модерд Садік


1
GetHashCode повинен використовувати всі ті ж поля та властивості, які використовуються для порівняння для рівності, а не лише одне з них. тобтоpublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG у СД

Для отримання інформації про створення хорошого алгоритму хешу: stackoverflow.com/questions/263400/…
JG в SD

0

Для того, щоб він працював у VB.NET, вам потрібно вказати Keyключове слово перед кожною властивістю анонімного типу, саме так:

myObjectCollection.Select(Function(item) New With
{
    Key .Alpha = item.propOne,
    Key .Bravo = item.propTwo
}).Distinct()

Я боровся з цим, я думав, що VB.NET не підтримує подібний тип функцій, але насправді це є.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.