自定义类的List.except

问题描述 投票:5回答:5

假设我有一个自定义类:

public class WineCellar
{

    public string year;
    public string wine;
    public double nrbottles;
}

可以说我现在有一个这个自定义类的列表:

List<WineCellar> orignialwinecellar = List<WineCellar>();

包含这些项目:

2012 Chianti 12

2011 Chianti 6

2012 Chardonay 12

2011 Chardonay 6

我知道,如果我想比较两个列表并返回一个新列表,其中只包含不在其他列表中的项目,我会这样做:

var newlist = list1.Except(list2);

如何将其扩展到自定义类?让我们说:

string[] exceptionwinelist = {"Chardonay", "Riesling"};

我想要退回:

List<WineCellar> result = originalwinecellar.wine.Except(exceptionwinelist);

这个伪代码显然不起作用,但希望能说明我想做的事情。然后,该shoudl返回自定义类winecellar的List,其中包含以下项:

2012年基安蒂12

2011年基安蒂6

谢谢。

c# linq
5个回答
15
投票

你真的不想在这里使用Except,因为你没有用作黑名单的WineCellar对象的集合。你所拥有的是一系列规则:“我不想要具有这种和这样的葡萄酒名称的物品”。

因此,最好简单地使用Where

List<WineCellar> result = originalwinecellar
    .Where(w => !exceptionwinelist.Contains(w.wine))
    .ToList();

以人类可读的形式:

我想要所有WineCellars,其中葡萄酒名称不在例外列表中。

顺便说一句,WineCellar类名称有点误导;那些物品不是酒窖,它们是库存物品。


5
投票

一种解决方案是使用扩展方法:

public static class WineCellarExtensions
{
    public static IEnumerable<WineCellar> Except(this List<WineCellar> cellar, IEnumerable<string> wines)
    {
        foreach (var wineCellar in cellar)
        {
            if (!wines.Contains(wineCellar.wine))
            {
                yield return wineCellar;
            }
        }
    }
}

然后像这样使用它:

List<WineCellar> result = originalwinecellar.Except(exceptionwinelist).ToList();

2
投票

exceptionWineListstring[]但是originalWineCellarList<WineCellar>WineCellar不是string,所以在这些之间执行Except是没有意义的。

你可以这么容易做到

// use HashSet for look up performance.
var exceptionWineSet = new HashSet<string>(exceptionWineList);
var result = orginalWineCellar.Where(w => !exceptionWineSet.Contains(w.Wine));

我认为你在你的问题中提到的是类似的东西

WineCellar : IEquatable<string>
{
    ...
    public bool Equals(string other)
    {
        return other.Equals(this.wine, StringComparison.Ordinal);
    }
}

这允许你将WineCellars等同于strings。


但是,如果我要修改你的模型,我会想出类似的东西,

enum WineColour
{
    Red,
    White,
    Rose
}

enum WineRegion
{
    Bordeaux,
    Rioja,
    Alsace,
    ...
}

enum GrapeVariety
{
    Cabernet Sauvignon,
    Merlot,
    Ugni Blanc,
    Carmenere,
    ...
}

class Wine
{
    public string Name { get; set; }
    public string Vineyard { get; set; }
    public WineColour Colour { get; set; }
    public WineRegion Region { get; set; }
    public GrapeVariety Variety { get; set; }
}

class WineBottle
{
    public Wine Contents { get; set; }
    public int Millilitres { get; set; }
    public int? vintage { get; set; }
}

class Bin : WineBottle
{
    int Number { get; set; }
    int Quantity { get; set; }
}

class Cellar : ICollection<WineBottle> 
{
    ...
}

然后,你可以看到有几种方法来比较Wine,我可能想要在Cellar的一个或多个属性上过滤Wine。因此,我可能会为自己提供一些灵活性,

class WineComparer : EqualityComparer<Wine>
{
    [Flags]
    public Enum WineComparison
    {
        Name = 1,
        Vineyard= 2,
        Colour = 4,
        Region = 8,
        Variety = 16,
        All = 31
    }

    private readonly WineComparison comparison;

    public WineComparer()
        : this WineComparer(WineComparison.All)
    {
    }

    public WineComparer(WineComparison comparison)
    {
        this.comparison = comparison;
    }

    public override bool Equals(Wine x, Wine y)
    {
        if ((this.comparison & WineComparison.Name) != 0
            && !x.Name.Equals(y.Name))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Vineyard) != 0
            && !x.Vineyard.Equals(y.Vineyard))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Region) != 0
            && !x.Region.Equals(y.Region))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Colour) != 0
            && !x.Colour.Equals(y.Colour))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Variety) != 0
            && !x.Variety.Equals(y.Variety))
        {
            return false;
        }

        return true;
    }

    public override bool GetHashCode(Wine obj)
    {
        var code = 0;
        if ((this.comparison & WineComparison.Name) != 0)
        {
            code = obj.Name.GetHashCode();
        }

        if ((this.comparison & WineComparison.Vineyard) != 0)
        {
            code = (code * 17) + obj.Vineyard.GetHashCode();
        }

        if ((this.comparison & WineComparison.Region) != 0)
        {
            code = (code * 17) + obj.Region.GetHashCode();
        }

        if ((this.comparison & WineComparison.Colour) != 0)
        {
            code = (code * 17) + obj.Colour.GetHashCode();
        }

        if ((this.comparison & WineComparison.Variety) != 0)
        {
            code = (code * 17) + obj.Variety.GetHashCode();
        }

        return code;
    }
}

这可能看起来很费劲但它有一些用处。让我们说我们想在你的酒窖里除了红里奥哈之外的所有葡萄酒,你可以做点什么,

var comparison = new WineComparer(
    WineComparison.Colour + WineComparison.Region);

var exception = new Wine { Colour = WineColour.Red, Region = WineRegion.Rioja }; 

var allButRedRioja = cellar.Where(c => 
    !comparison.Equals(c.Wine, exception));

0
投票

要直接对泛型类使用此类扩展方法,您应该实现比较器。它由两个方法组成:Equal和GetHashCode。您应该在WineCellar类中实现它们。 Note the second example

请注意,基于哈希的方法比基本的“List.Contains ...”实现快得多。


0
投票

我有同样的问题。我尝试了Darren的例子,但无法让它正常工作。

因此,我对Darren的例子进行了如下修改:

static class Helper
{
    public static IEnumerable<Product> Except(this List<Product> x, List<Product> y)
    {
        foreach(var xi in x)
        {
            bool found = false;
            foreach (var yi in y) { if(xi.Name == yi.Name) { found = true; } }
            if(!found) { yield return xi; }
        }
    }
}

这适合我。如果需要,您可以在if子句中添加几个字段。

© www.soinside.com 2019 - 2024. All rights reserved.