Як конвертувати результати linq в HashSet або HashedSet


196

У мене є властивість класу, який є ISet. Я намагаюся отримати результати запиту linq у цю властивість, але не можу зрозуміти, як це зробити.

В основному, шукаю останню частину цього:

ISet<T> foo = new HashedSet<T>();
foo = (from x in bar.Items select x).SOMETHING;

Можна також зробити це:

HashSet<T> foo = new HashSet<T>();
foo = (from x in bar.Items select x).SOMETHING;

Я думаю, ви повинні залишити поза HashedSetчастиною. Це просто заплутано, оскільки C#і LINQнічого не називається HashedSet.
Джогге

Відповіді містяться у скриньках відповідей, а не в самому питанні. Якщо ви хочете відповісти на власне запитання, що ідеально відповідає правилам SO, напишіть на це відповідь.
Адріан

Відповіді:


308

Я не думаю, що в цьому є щось побудоване, але написати метод розширення дуже просто:

public static class Extensions
{
    public static HashSet<T> ToHashSet<T>(
        this IEnumerable<T> source,
        IEqualityComparer<T> comparer = null)
    {
        return new HashSet<T>(source, comparer);
    }
}

Зауважте, що ви дійсно бажаєте тут методу розширення (або принаймні загального методу якоїсь форми), тому що ви, можливо, не зможете явно висловити тип T:

var query = from i in Enumerable.Range(0, 10)
            select new { i, j = i + 1 };
var resultSet = query.ToHashSet();

Ви не можете цього зробити з явним викликом HashSet<T>конструктору. Ми покладаємось на висновок типу для загальних методів, щоб зробити це для нас.

Тепер ви можете вибрати назву ToSetта повернутись ISet<T>- але я б дотримувався ToHashSetі конкретного типу. Це узгоджується зі стандартними операторами LINQ ( ToDictionary, ToList) і дозволяє в майбутньому розширюватися (наприклад ToSortedSet). Ви також можете надати перевантаження із зазначенням використовуваного порівняння.


2
Добре бачити анонімні типи. Однак чи мають анонімні типи корисні зміни GetHashCodeта Equals? Якщо ні, то вони не стануть хорошими в HashSet ...
Джоель Мюллер,

4
@Joel: Так, так і є. В іншому випадку всі речі будуть виходити з ладу. Справді, вони використовують лише порівняльники рівності за замовчуванням для типів компонентів, але це зазвичай досить добре.
Джон Скіт

2
@JonSkeet - чи знаєте ви, чи є якась причина, чому це не постачається в стандартних операторах LINQ поряд з ToDictionary і ToList? Я чув, що часто є вагомі причини, через які такі речі опущені, подібні до відсутнього методу IEnumerable <T> .ForEach
Стівен Холт

1
@StephenHolt: Я погоджуюся з опором ForEach, але ToHashSetмає набагато більше сенсу - я не знаю жодної причини не робити ToHashSetіншого, ніж звичайне "не відповідає смузі корисності" (з чим я не згоден, але ...)
Джон Скіт

1
@Aaron: Не впевнений ... можливо, тому що це не буде так просто для постачальників, які не є LINQ-до-об'єктів? (Я не можу уявити, що це було б нездійсненно.)
Джон Скіт

75

Просто передайте свій IEnumerable у конструктор для HashSet.

HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);

2
Як ви збираєтеся впоратися з проекцією, що призводить до анонімного типу?
Джон Скіт

7
@Jon, я вважаю, що це YAGNI, поки не підтвердиться інше.
Ентоні Пеграм

1
Ясно, що я ні. На щастя, у цьому конкретному прикладі мені цього не потрібно.
Джоель Мюллер

@Jon тип задається ОП (перший рядок не буде компілятором інакше), тому в цьому випадку я повинен погодитися, що це YAGNI зараз
Rune FS

2
@Rune FS: У цьому випадку я підозрюю, що зразок коду не реалістичний. Досить рідко є параметр типу T, але знайте, що кожен елемент bar.Itemsбуде T. З огляду на те, як легко зробити цю загальну мету і як часто анонімні типи з'являються в LINQ, я думаю, що варто зробити додатковий крок.
Джон Скіт


19

Як заявив @Joel, ви можете просто передати свій номер. Якщо ви хочете зробити метод розширення, ви можете:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}

3

Якщо вам потрібен лише доступ лише для читання лише до набору, а джерело є параметром для вашого методу, я б пішов з

public static ISet<T> EnsureSet<T>(this IEnumerable<T> source)
{
    ISet<T> result = source as ISet<T>;
    if (result != null)
        return result;
    return new HashSet<T>(source);
}

Причина полягає в тому, що користувачі можуть викликати ваш метод ISetвже тим, що вам не потрібно створювати копію.


3

Існує побудова методу розширення в .NET Framework та в ядро ​​.NET для перетворення IEnumerableв HashSet: https://docs.microsoft.com/en-us/dotnet/api/?term=ToHashSet

public static System.Collections.Generic.HashSet<TSource> ToHashSet<TSource> (this System.Collections.Generic.IEnumerable<TSource> source);

Здається, я поки не можу використовувати його у стандартних бібліотеках .NET (на момент написання). Тоді я використовую цей метод розширення:

    [Obsolete("In the .NET framework and in NET core this method is available, " +
              "however can't use it in .NET standard yet. When it's added, please remove this method")]
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null) => new HashSet<T>(source, comparer);

2

Це досить просто :)

var foo = new HashSet<T>(from x in bar.Items select x);

і так, T - тип, визначений ОП :)


Це не визначає аргумент типу.
Джон Скіт

Лол, який повинен стати найсуворішим голосом за день :). Для мене зараз точно така ж інформація. Побиває мене, чому система виводу типу вже не підводить цього типу :)
Rune FS

Скасуйте зараз ... але це насправді досить важливо саме тому, що ви не отримуєте висновок про тип. Дивіться мою відповідь.
Джон Скіт

2
Я не пам'ятаю, як бачив його в блозі Еріка, чому висновок про конструкторів не є частиною специфікації, ти знаєш чому?
Руна ФС

1

Відповідь Джона ідеальна. Єдине застереження полягає в тому, що, використовуючи HashedSet NHibernate, мені потрібно перетворити результати в колекцію. Чи існує оптимальний спосіб зробити це?

ISet<string> bla = new HashedSet<string>((from b in strings select b).ToArray()); 

або

ISet<string> bla = new HashedSet<string>((from b in strings select b).ToList()); 

Або я пропускаю щось інше?


Редагувати: Ось що я закінчив:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

public static HashedSet<T> ToHashedSet<T>(this IEnumerable<T> source)
{
    return new HashedSet<T>(source.ToHashSet());
}

ToListяк правило, більш ефективно, ніж ToArray. Це просто потрібно реалізувати ICollection<T>?
Джон Скіт

Правильно. У кінцевому підсумку я перейшов до вашого методу, а потім скористався цим, щоб зробити ToHashedSet (див. Редагування на оригінальне запитання). Спасибі за вашу допомогу.
Джеймі

0

Замість простого перетворення IEnumerable в HashSet, часто зручно перетворювати властивість іншого об'єкта в HashSet. Ви можете написати це так:

var set = myObject.Select(o => o.Name).ToHashSet();

але я вважаю за краще використовувати селектори:

var set = myObject.ToHashSet(o => o.Name);

Вони роблять те саме, і друге, очевидно, коротше, але я вважаю, що ідіома краще відповідає моєму мозку (я думаю, що це як ToDictionary).

Ось метод розширення, який слід використовувати, як підтримка користувацьких порівняльників як бонус.

public static HashSet<TKey> ToHashSet<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> selector,
    IEqualityComparer<TKey> comparer = null)
{
    return new HashSet<TKey>(source.Select(selector), comparer);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.