使用LINQ获取唯一项目列表

22

我已经使用LINQ有一段时间了,但在处理唯一项时似乎卡住了,我有以下列表:

List<Stock> stock = new List<Stock>();

这个对象具有以下属性: 字符串ID,字符串类型,字符串描述,例如:

public class Stock
{
    public string ID { get; set; }
    public string Type { get; set; }
    public string Description { get; set; }
}

我希望有一个LINQ查询,可以按照Stock中的Type对项进行分组,并将其作为新的Stock列表返回(必须与原始Stock列表具有相同的类型)。

示例数据:

 ID   Type                 Description
----------------------------------------------
  1   Kitchen Appliance    Dishwasher  
  2   Living Room          Television  
  3   Kitchen Appliance    Washing Machine  
  4   Kitchen Appliance    Fridge  
我的Linq查询想要能够返回所有的厨房电器,例如。因此,我会将其作为“类型”传递给查询,它将从这个示例列表中返回项目1、3和4。
此返回的列表也必须是类型:List<Stock>
本质上,我想要一个按照类型唯一项的列表,有点像SQL Distinct查询,如何在LINQ中实现呢?替代方案可以使用Silverlight / C#客户端代码。
另一个澄清是,我也可能不提供参数“厨房电器”,只想要唯一的,例如它将只返回每种类型的Kitchen Appliance和Living Room一次,以某种方式表示类别,不管这种类型有多少个。

1
我不明白为什么你要在这里提到“distinct”和“unique”,这只是一个简单的Linq查询,对象已经是唯一的了。 - Lasse V. Karlsen
2
我稍微修改了一下你的数据和代码描述,让它们更易于阅读,希望你不介意。 - Lasse V. Karlsen
@p.campbell - 我的列表包含重复类型 - 我想将它们缩小,使所有类型都作为一个组合在一起,因此如果列表中有10个“厨房电器”类型和一个“客厅”,它将仅返回每种类型的一个作为“顶级”列表。 我想要一个分组行为 - 就像Windows文件夹等一样。 - RoguePlanetoid
那么你的问题并不是很清楚,你说你将类型提供给查询并想要所有电器返回,但那并不是你真正想要的。 - Lasse V. Karlsen
我想要按类型列出独特的项目列表,类似于SQL Distinct查询,我该如何在LINQ中实现这一点? 我认为我不明白为什么您不能只使用Distinct()扩展,因为它本质上与SQL的“distinct”关键字执行相同的操作。请告诉我我错过了什么。 - BrainSlugs83
显示剩余5条评论
6个回答

52

灵活的方法

使用GroupByToDictionary创建一个以Type属性为键的List<Stock>值字典:

var appliancesByType = stock
    .GroupBy(item => item.Type)
    .ToDictionary(grp => grp.Key, grp => grp.ToList());

那么您可以很容易地访问类型本身以及任何给定类型的列表:

// List of unique type names only
List<string> stockTypes = appliancesByType.Keys.ToList();

// Or: list of one stock item per type
List<Stock> exampleStocks = appliancesByType
    .Select(kvp => kvp.Value[0])
    .ToList();

// List of stock items for a given type
List<Stock> kitchenAppliances = appliancesByType["Kitchen Appliance"];

在我看来,这种方法确实可以满足你所有的需求。但是如果需要其他选项,请参见以下内容。

备选方法(快速简单)

您可以始终使用Where获取所需类型的项目,然后使用ToList将这些项目放入新的List<Stock>中:

List<Stock> kitchenAppliances = stock
    .Where(item => item.Type == "Kitchen Appliance")
    .ToList();

针对最后一段说的:

再澄清一下,我可能不会提供“厨房电器”这个参数,只想要唯一的东西,例如,它将每种类型的Kitchen Appliance 和 Living Room 只返回一次,就像一个类别一样,无论有多少个Type。

在这里,您似乎想要完全不同的东西:基本上是由 Distinct 提供的行为。为此功能,您可以基本上采用 Soonts's answer(可选地,返回一个 IEnumerable<tKey> 而不是 IEnumerable<tSource>),或者只需利用DistinctSelect组合,以避免需要实现一个IEqualityComparer<Stock> (请参见以下内容)。


更新

针对您的澄清,这是我的建议:两种方法,每种方法各自用于一个目的(单一职责原则):

// This will return a list of all Stock objects having the specified Type
static List<Stock> GetItemsForType(string type)
{
    return stock
        .Where(item => item.Type == type)
        .ToList();
}

// This will return a list of the names of all Type values (no duplicates)
static List<string> GetStockTypes()
{
    return stock
        .Select(item => item.Type)
        .Distinct()
        .ToList();
}

如果我提供了一个类型,这个代码可以正常工作,但是我忘记说(并且已经编辑了我的问题),如果没有提供类型,我希望列表中有一个“厨房电器”和“客厅” - 但这对我仍然有用。无论选择哪个项目,都不重要,因为在此用途中,类型将被使用,无论它是否唯一。 - RoguePlanetoid
@RoguePlanetoid:所以你基本上希望类型参数是可选的——如果指定了,将返回该类型的所有项目;否则,将返回每种类型的一个项目?在我看来,这是一个奇怪的API;我建议以两个单独的方法形式实现此功能(例如GetStockForTypeGetStockTypes)。 - Dan Tao
我有一种感觉,需要使用比较器才能使其正常工作,这里的解决方案对于我提供类型时是可以的,但如果我可以将一个类型与另一个类型进行比较以获取唯一的类型,那么这将是可以的,只需要将其转换为LINQ表达式即可。 两种方法都可以,因为它们将在不同的地方使用。 - RoguePlanetoid
@RoguePlanetoid:对于您所描述的第二个函数(唯一类型列表),我认为Distinct就足够了。请查看我的更新。 - Dan Tao
感谢更新,第二部分为我提供了唯一类型的列表 - 我只需要该类型的一个股票项目,以便函数返回相同的类型。谢谢,标记为答案。 - RoguePlanetoid
刚刚在我的代码中使它工作了 - 再次感谢!非常好用,正是我想要的 - 很抱歉之前有点含糊不清! - RoguePlanetoid

4
听起来只需要一个简单的Where子句就可以了。
List<Stock> kitchen= stock.Where(s=>s.Type=="Kitchen Appliance")
                          .OrderBy(s=>s.Description).ToList();

如果你只想获取源列表中包含的类型

string[] typesFound = stock.Select(s=>s.Type).Distinct().ToArray();


3
static class EnumerableEx
{
    // Selectively skip some elements from the input sequence based on their key uniqueness.
    // If several elements share the same key value, skip all but the 1-st one.
    public static IEnumerable<tSource> uniqueBy<tSource, tKey>( this IEnumerable<tSource> src, Func<tSource, tKey> keySelecta )
    {
        HashSet<tKey> res = new HashSet<tKey>();
        foreach( tSource e in src )
        {
            tKey k = keySelecta( e );
            if( res.Contains( k ) )
                continue;
            res.Add( k );
            yield return e;
        }
    }
}

// Then later in the code
List<Stock> res = src.uniqueBy( elt => elt.Type ).ToList()

1
什么?OP希望能够获取所有的厨房电器,例如。你提供的代码将创建一个包含恰好一个厨房电器和每种其他类型的电器的列表。这似乎没有实现OP的目标。 - Dan Tao
是的,这样做无法实现目标。可以承认,原帖使用的词汇并不符合实际需求。 - p.campbell
@p.campbell:没错。特别是问题的最后一部分似乎措辞不清楚。 - Dan Tao
我不确定我需要什么 - 我卡在了问题上,不确定如何描述我需要什么,但我立刻得到了一半我需要的东西 - 所以只是我需要按相似类型分组的部分 - 直到询问后我才意识到。 - RoguePlanetoid
点赞这个不错的实现,但这不就是和Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)一样吗? - Sudhanshu Mishra

3
var kitchenAppliances = stocks.Where(stock => stock.Type == "Kitchen Appliance");

1
你用了ID而不是Type :) - Lasse V. Karlsen
1
可能还需要添加 .Distinct()。 - BrainSlugs83

1

我可能不理解这个问题,但对我来说,这似乎是一个相当简单的Linq查询:

List<Stock> stock = new List<Stock>();
... populate your list as per your example

List<Stock> kitchenAppliances =
    (from obj in stock
     where obj.Type == "Kitchen Appliance"
     select obj).ToList();

或者如果您更喜欢扩展方法语法:

List<Stock> kitchenAppliances =
    stock.Where(obj => obj.Type == "Kitchen Appliance").ToList();

我不理解你在这个上下文中使用“distinct”和“unique”的含义。这些对象已经是唯一的,而且我认为你想要一个基本的查询“给我所有的厨房电器”。

1
你也可以按照以下方式进行操作,此例中我正在获取lucene结果,然后将其转换为匿名类型以获得umbraco节点。
var datasource = (from n in SearchResults
select new
{
  PageLink = uQuery.GetNode(n.Id).NiceUrl,
  uQuery.GetNode(n.Id).Name,
  Date =    uQuery.GetNode(n.Id).GetProperty("startDate"),
  IntroText = string.IsNullOrEmpty(uQuery.GetNode(n.Id).GetProperty("introText").Value) ? string.Empty : uQuery.GetNode(n.Id).GetProperty("introText").Value,
  Image = ImageCheck(uQuery.GetNode(n.Id).GetProperty("smallImage").Value)

}).Distinct().ToList();

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