如何将linq结果转换为HashSet或HashedSet


195

我有一个作为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省略-part。因为这很混乱,C#而且LINQ没有任何名称HashedSet
Jogge

答案放在答案框中,而不是问题本身。如果您想回答自己的问题(按照SO规则完全可以解决),请为此写一个答案。
Adriaan

Answers:


307

我不认为有什么内置函数可以执行此操作……但是编写扩展方法确实很容易:

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运算符()一致ToDictionaryToList并允许将来进行扩展(例如ToSortedSet)。您可能还需要提供一个重载,以指定要使用的比较。


2
关于匿名类型的要点。然而,做匿名类型有有用的覆盖GetHashCodeEquals?如果不是这样的话,他们在HashSet中的表现将不会很好...
Joel Mueller 2010年

4
@Joel:是的,他们愿意。否则,各种事情都会失败。诚然,他们只对组件类型使用默认的相等比较器,但这通常就足够了。
乔恩·斯基特

2
@JonSkeet-您是否知道是否有任何原因导致标准LINQ运算符与ToDictionary和ToList一起未提供?我听说有很多很好的理由可以省略这些内容,类似于缺少的IEnumerable <T> .ForEach方法
Stephen Holt

1
@StephenHolt:我同意对的抵制ForEach,但ToHashSet更有意义-我不知道ToHashSet除了正常的“不符合实用性标准”(我不同意,但是...)
乔恩·斯基特

1
@Aaron:不确定...可能是因为对于非LINQ-to-Objects提供程序而言,它不是那么简单吗?(承认,我无法想象这是不可行的。)
乔恩·斯基特

75

只需将IEnumerable传递给HashSet的构造函数即可。

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

2
您将如何处理导致匿名类型的投影?
乔恩·斯基特

7
@Jon,除非另有确认,否则我认为这是YAGNI。
安东尼·佩格拉姆

1
显然我不是。幸运的是,在此特定示例中,我不需要这样做。
乔尔·穆勒

@Jon类型由OP指定(第一行不会进行编译),因此在这种情况下,我必须同意现在使用YAGNI
Rune FS 2010年

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框架和.NET核心扩展方法构建用于转换IEnumerable到一个HashSethttps://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是OP指定的类型:)


那没有指定类型参数。
乔恩·斯基特

大声笑,必须是今天最严厉的否决票:)。对我来说,现在有完全相同的信息。困扰我为什么类型推断系统还没有推断出该类型:)
Rune FS 2010年

现在撤消...但这实际上非常重要,恰恰是因为您没有得到类型推断。看我的答案。
乔恩·斯基特

2
我不记得在Eric的博客上看到过它,为什么对构造函数的推断不属于规范的一部分,您知道为什么吗?
符文FS 2010年

1

乔恩的答案很完美。唯一需要注意的是,使用NHibernate的HashedSet,我需要将结果转换为集合。有没有最佳的方法来做到这一点?

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.