自定义类的List.except

时间:2013-07-22 08:13:53

标签: c# linq

假设我有一个自定义类:

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 Chianti 12

2011 Chianti 6

感谢。

5 个答案:

答案 0 :(得分:15)

你真的不想在这里使用Except,因为你没有一组WineCellar个对象可以用作黑名单。你所拥有的是一系列规则:“我不希望有这样和那样的葡萄酒名称的对象”。

因此,最好只使用Where

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

以人类可读的形式:

  

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

顺便说一句,WineCellar班级名称有点误导;这些物品不是酒窖,而是库存物品。

答案 1 :(得分: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 :(得分: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);
    }
}

允许您将WineCellar等同于string s。


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

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

答案 3 :(得分:0)

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

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

答案 4 :(得分: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子句中添加几个字段。