如何将Linq查询结果转换为HashSet或HashedSet。

223

我有一个类的属性,它是一个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的东西,所以这只会让人感到困惑。 - Jogge
请在答案框中回答,而不是在问题本身中。如果您想回答自己的问题(根据SO规则这完全没问题),请为其编写一个答案。 - Adriaan
9个回答

330

我不认为有任何内置的方法可以做到这一点...但编写扩展方法非常容易:

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
4
是的,它们有。否则,各种东西都会失败。诚然,它们只使用组件类型的默认相等比较器,但通常足够好。 - Jon Skeet
2
@JonSkeet - 你知道为什么这个方法没有和ToDictionary和ToList一起被提供在标准的LINQ操作符中吗?我听说通常会有很好的理由来省略这样的东西,就像缺少的IEnumerable<T>.ForEach方法一样。 - Stephen Holt
1
@StephenHolt:我同意反对ForEach,但ToHashSet更有意义——除了正常的“不达到有用性要求”(我不同意,但...)外,我不知道不使用ToHashSet的任何理由。 - Jon Skeet
抱歉@JonSkeet。让我尝试更具体一些:假设我们有一个来自实体框架(Entity Framework)的IQueryable,我想将其转换为HashSet(因为看起来更“快”和“便宜”)。我想知道这种转换是否需要更多处理并且会因为转换而失去性能。 - Dan
显示剩余8条评论

88

将您的IEnumerable传递到HashSet的构造函数中即可。

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

2
你将如何处理导致匿名类型的投影? - Jon Skeet
7
@Jon,我认为在确认之前,这很可能是YAGNI。 - Anthony Pegram
1
显然我不需要。幸运的是,在这个特定的例子中,我不需要。 - Joel Mueller
@Jon,类型由OP指定(否则第一行将无法编译),因此在这种情况下,我不得不同意现在它是YAGNI。 - Rune FS
2
@Rune FS:在这种情况下,我怀疑示例代码并不现实。很少有类型参数T,但知道bar.Items的每个项目都将是T。考虑到使其通用非常容易,并且匿名类型在LINQ中经常出现,我认为值得再走一步。 - Jon Skeet
@Jon同意了,这就是为令他得到+1的原因。在这种特定情况下可能并不必要,但将其包装在通用方法中的方法作为一个好的例子,对所有谷歌用户都很有帮助。 - Rune FS

50

8
也包含在 netcore 2.0 及更高版本 中。 - mbx

20

正如@Joel所说,你可以直接传递你的可枚举对象。如果你想使用扩展方法,可以这样做:

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

4

在.NET框架和.NET core中,有一个扩展方法可以将IEnumerable转换为HashSethttps://learn.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 Standard库中使用它。因此,我使用了这个扩展方法:

    [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);

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
这很简单 :)
var foo = new HashSet<T>(from x in bar.Items select x);

是的,T是由OP指定的类型 :)


那没有指定类型参数。 - Jon Skeet
哈哈,那一定是今天最严厉的反对票了 :)。对我来说,现在有完全相同的信息。我不知道为什么类型推断系统还没有自动推断出那种类型 :)。 - Rune FS
现在还没有完成...但这实际上非常重要,正是因为你没有得到类型推断。请看我的回答。 - Jon Skeet
2
我不记得在Eric的博客上看到过为什么构造函数的推断不是规范的一部分,你知道为什么吗? - Rune FS

2

Jon的答案非常完美。唯一的注意点是,使用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> 接口吗? - Jon Skeet
没错。我最终采用了你的方法,然后使用它来执行ToHashedSet(请参见原始问题上的编辑)。谢谢你的帮助。 - Jamie

1

与其简单地将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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接