List.except in custom class

lets say that I have a custom class:

public class WineCellar { public string year; public string wine; public double nrbottles; } 

Suppose I now have a list of this custom class:

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

containing these elements:

 2012 Chianti 12 2011 Chianti 6 2012 Chardonay 12 2011 Chardonay 6 

I know that if I want to compare two lists and return a new list that has only those elements that are not in another list, I would do:

 var newlist = list1.Except(list2); 

How can I extend this to a custom class? Let's say I have:

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

I would like this to be returned:

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

This pseudo code is clearly not working, but hopefully illustrates what I'm trying to do. Then shoudl returns a list of the custom winecellar class with the following elements:

2012 Chianti 12

2011 Chianti 6

Thanks.

+4
source share
5 answers

You really do not want to use Except here, since you do not have a collection of WineCellar objects to use as a blacklist. You have a collection of rules: "I do not want objects with such and such wine names."

Therefore, it is better to simply use Where :

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

In readable form:

I want all WineCellars where the wine name is not on the exception list.

As an aside, the WineCellar class WineCellar little misleading; these objects are not basements; they are elements of inventory.

+12
source

One solution has an extension method:

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

And then use it like this:

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

exceptionWineList is string[] , but originalWineCellar is List<WineCellar> , WineCellar not string , so it makes no sense to execute Except between them.

You could just as easily do

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

I think you are hinting at your question, is it something like

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

which allows you to equate WineCellar with string s.


However, if I were to rework your model, I would come up with something like

 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> { ... } 

Then you can see that there are several ways to compare Wine , and I can filter out Cellar for one or more Wine properties. Therefore, I may be inclined to be somewhat flexible,

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

it probably seems like a lot of effort, but it makes sense. Say we wanted all the wine except the red Rioja in your basement to do something like:

 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)); 
+2
source

To use these extension methods with common classes, you must use a comparator. It consists of two methods: Equal and GetHashCode. You must implement them in your WineCellar class. Pay attention to the second example .

Note that hash-based methods are much faster than the underlying implementations of List.Contains ....

0
source

I had the same problem. I tried an example from Darren, but could not get it to work correctly.

So I made a modification from Darren's example as follows:

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

This works for me. You can add several fields to the if clause if necessary.

0
source

Source: https://habr.com/ru/post/1492654/


All Articles