C# 列表。自定义类上除外

C# 列表。自定义类上除外,c#,linq,C#,Linq,假设我有一个自定义类: public class WineCellar { public string year; public string wine; public double nrbottles; } List<WineCellar> orignialwinecellar = List<WineCellar>(); 假设我现在有一个自定义类的列表: public class WineCellar { public strin

假设我有一个自定义类:

public class WineCellar
{

    public string year;
    public string wine;
    public double nrbottles;
}
List<WineCellar> orignialwinecellar = List<WineCellar>();
假设我现在有一个自定义类的列表:

public class WineCellar
{

    public string year;
    public string wine;
    public double nrbottles;
}
List<WineCellar> orignialwinecellar = List<WineCellar>();
我知道,如果我想比较两个列表并返回一个新列表,其中只包含不在另一个列表中的项目,我会:

var newlist = list1.Except(list2);
如何将其扩展到自定义类?假设我有:

string[] exceptionwinelist = {"Chardonay", "Riesling"};
我想把这个退回:

List<WineCellar> result = originalwinecellar.wine.Except(exceptionwinelist);
List result=originalwinecillar.wine.Except(例外WineList);
这个伪代码显然不起作用,但希望能说明我正在尝试做什么。然后返回一个自定义类winecellar列表,其中包含以下项目:

2012基安蒂12

2011基安蒂6号


谢谢。

除了这里,你不想使用
,因为你没有
酒窖
对象的集合来用作黑名单。你所拥有的是一系列规则:“我不想要有这样或那样酒名的东西”

因此,最好只使用
Where

List<WineCellar> result = originalwinecellar
    .Where(w => !exceptionwinelist.Contains(w.wine))
    .ToList();
列表结果=原始酒窖
.Where(w=>!exceptionwinelist.Contains(w.wine))
.ToList();
以人类可读的形式:

我希望所有酒窖的葡萄酒名称不在例外列表中


另外,
winecillar
类名有点误导;这些对象不是酒窖,而是库存物品。

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

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;
            }
        }
    }
}
公共静态类扩展
{
公共静态IEnumerable除外(此列表酒窖,IEnumerable葡萄酒)
{
foreach(酒窖中的var酒窖)
{
如果(!葡萄酒。包含(酒窖。葡萄酒))
{
产量回收酒窖;
}
}
}
}
然后像这样使用它:

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

例外WineList
是一个
字符串[]
,但是
原始酒窖
是一个
列表
Winecella
不是
字符串
,因此在它们之间执行
之外的
是没有意义的

你可以很容易地做到

// use HashSet for look up performance.
var exceptionWineSet = new HashSet<string>(exceptionWineList);
var result = orginalWineCellar.Where(w => !exceptionWineSet.Contains(w.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;
    }
}

要直接将这种扩展方法用于泛型类,您应该实现comparator。它由两个方法组成:Equal和GetHashCode。您应该在WineCellar类中实现它们。 .


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

我也有同样的问题要解决。我尝试了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; }
        }
    }
}
静态类助手
{
公共静态IEnumerable除外(此列表x,列表y)
{
(X中的var席)
{
bool-found=false;
foreach(y中的变量yi){if(xi.Name==yi.Name){found=true;}}
如果(找到){收益返回席;}
}
}
}

这对我有用。如果需要,您可以在if子句中添加多个字段。

如果您非常频繁地进行此排序,或者异常列表包含许多项,您可能希望将异常列表存储为
哈希集
,以使
包含
功能更快。然而,像往常一样,将这两种方法作为
HashSet
进行基准测试确实会产生不可忽略的成本。@Jon,不错的一个。。。我想Waayy在这里太远了。。。但是,请注意演员表中的
列表
,-)@是的,我错过了——现在修好了。@ScottChamberlain:没错。我没有走那么远,因为如果这真的需要大量处理,那么对于输入数据来说,直接的
列表也不是一个好的选择,因此结果与原始问题没有太多共同之处。每次我在数组上看到一个使用
Contains
的问题。我只是想帮助别人从我的错误中吸取教训:)很好的扩展方法!我要做的唯一不同的事情是将第一个参数更改为
this IEnumerable Cell
,使用contains to List会导致非常糟糕的性能,因为contains on List是O(n)操作,它在每个步骤上都会执行。在这种情况下,应该通过HashSet或Dictionary完成。@gleb.kudr我知道。除此之外,这是一个不同的问题(如何优化运行缓慢的代码),取决于输入。当然继续并将
wines
参数隐藏在
HashSet
中。最好的方法是实现GetHashCode。这种“此列表”扩展将泛型的核心概念搞砸了,因为您有一个通用的实现。
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));
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; }
        }
    }
}