Odd Linq behavior with IList / IEnumerable - c#

I've got the following code:
public IList<IProductViewModel> ChildProducts { get; set; }
public IList<IProductViewModel> GiftItems { get; set; }
public IList<IProductViewModel> PromoItems { get; set; }
public IList<IProductViewModel> NonGiftItems
{
get
{
return NonPromoItems.Except(GiftItems, new ProductViewModelComparer()).ToList();
}
}
public IList<IProductViewModel> NonPromoItems
{
get
{
return ChildProducts.Where(p => !p.IsPromotion).ToList();
}
}
ProductViewModelComparer:
public class ProductViewModelComparer : IEqualityComparer<IProductViewModel>
{
#region IEqualityComparer<IProductViewModel> Members
public bool Equals(IProductViewModel x, IProductViewModel y)
{
if (Object.ReferenceEquals(x, y))
return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return String.Equals(x.ProductId, y.ProductId);
}
public int GetHashCode(IProductViewModel obj)
{
return obj.ProductId.GetHashCode();
}
#endregion
}
So basically, NonPromoItems is (ChildProducts - PromoItems) and NonGiftItems is (NonPromoItems - GiftItems)
However When:
ChildProducts = IEnumerable<IProductViewModel>[6]
PromoItems = IEnumerable<IProductViewModel>[1] where item matches 1 item in ChildProducts
GiftItems = IEnumerable<IProductViewModel>[0]
My Result is
NonPromoItems = IEnumerable<IProductViewModel>[5] This is Correct
NonGiftItems = IEnumerable<IProductViewModel>[4] This is Incorrect
Somehow an Except(...) is removing an item when given an empty list to subtract.
Any ideas anyone?

Is the removed item a duplicate of another in the list? If so, this is standard behavior for the Except method call:
The Except<T> is a little tricky in that instead of returning the difference as you might expect, it instead returns the set difference. A mathematical set does not contain duplicates (e.g. HashSet).
Here's an example of what I mean:
int[] ints = { 3, 3, 3 };
var result = ints.Except(5); // result contains one element only: { 3 }
You can read more on this CodeProject thread in which I described the problem.
To get "Except"-like functionality while leaving duplicates, you can write the following line:
var ints = new [] { 3, 42, 3 };
var excluded = new [] { 42 };
var results = ints.Where(i => !excluded.Contains(i)); // returns { 3, 3 }

Related

Simplest method to prove that the contents of two lists (containing objects) are equal

I am having a bit of a frustrating time finding a simple method to compare and prove that the contents of two lists are equal. I have looked at a number of solutions on stackoverflow but I have not been successful. Some of the solutions look like they will require a large amount of work to implement and do something that on the face of it to my mind should be simpler, but perhaps I am too simple to realize that this cannot be done simply :)
I have created a fiddle with some detail that can be viewed here: https://dotnetfiddle.net/cvQr5d
Alternatively please find the full example below, I am having trouble with the object comparison method (variable finalResult) as it's returning false and if the content were being compared I would expect the value to be true:
using System;
using System.Collections.Generic;
using System.Linq;
public class ResponseExample
{
public Guid Id { get; set; } = Guid.Parse("00000000-0000-0000-0000-000000000000");
public int Value { get; set; } = 0;
public string Initials { get; set; } = "J";
public string FirstName { get; set; } = "Joe";
public string Surname { get; set; } = "Blogs";
public string CellPhone { get; set; } = "0923232199";
public bool EmailVerified { get; set; } = false;
public bool CellPhoneVerified { get; set; } = true;
}
public class Program
{
public static void Main()
{
var responseOne = new ResponseExample();
var responseTwo = new ResponseExample();
var responseThree = new ResponseExample();
var responseFour = new ResponseExample();
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseThree);
objectListTwo.Add(responseFour);
bool result = objectListOne.Count == objectListTwo.Count();
Console.WriteLine($"Count: {result}");
bool finalResult = ScrambledEquals<ResponseExample>(objectListOne, objectListTwo);
Console.WriteLine($"Object compare: {finalResult}");
}
//https://stackoverflow.com/a/3670089/3324415
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
var cnt = new Dictionary<T,
int>();
foreach (T s in list1)
{
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T s in list2)
{
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
}
As people in comments have pointed out this will not work as comparing a complex type by default compares whether the reference is the same. Field by field comparison will not work without implementing equality methods (and then you would need to overload GetHashCode and so on). See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-5.0
However, if you can use c# 9, which is what you have in the fiddle you can define the type as a record instead of class. Records have built in field by field comparison. See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#characteristics-of-records
So public class ResponseExample would become public record ResponseExample and your code works as you expect.
Use Enumerable.All<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>) Method which Determines whether all elements of a sequence satisfy a condition.
Once you have initilized your two List
list1.All(x=>list2.Contains(x))
This works by ensuring that all elements in list2 are containted in list1 otherwise returns false
Your method as is will compare if the 2 lists contain the same objects. So it is returning false as there are 4 different objects. If you create your list like this, using the same objects, it will return true:
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseTwo);
objectListTwo.Add(responseOne);
To get a true value when the contents of the objects are the same you could serialize the objects into a json string like this:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
JavaScriptSerializer json = new JavaScriptSerializer();
var cnt = new Dictionary<string,
int>();
foreach (T _s in list1)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T _s in list2)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
If the performance is not a big deal, you can use Newtonsoft.Json. We will be able to compare different types of objects as well as run a deep equals check.
First install the package:
Install-Package Newtonsoft.Json
Here is the code snip:
public static bool DeepEqualsUsingJson<T>(IList<T> l1, IList<T> l2)
{
if (ReferenceEquals(l1, l2))
return true;
if (ReferenceEquals(l2, null))
return false;
if (l1.Count != l2.Count)
return false;
var l1JObject = l1.Select(i => JObject.FromObject(i)).ToList();
var l2JObject = l2.Select(i => JObject.FromObject(i)).ToList();
foreach (var o1 in l1JObject)
{
var index = l2JObject.FindIndex(o2 => JToken.DeepEquals(o1, o2));
if (index == -1)
return false;
l2JObject.RemoveAt(index);
}
return l2JObject.Count == 0;
}

How do I fix this bug: Cant remove object from a list?

I have this really weird bug where I cannot seem to delete an object from a list. I am using an Injector class to pass the same object(thus the same list) around into multiple forms. The Delete() function is just a generic delete, really nothing special. However in my unit test it keeps failing and I'm unsure why.
Injector class:
[Serializable]
public class InjectorClass
{
public OrderAdministration Administration { get; private set; }
public WareHouse WareHouse { get; private set; }
public InjectorClass()
{
Administration = new OrderAdministration(this);
WareHouse= new WareHouse();
}
}
Constructor of the WareHouse class:
public List<Part> Catalogus { get; private set; }
public List<Part> PartsInStock { get; private set; }
public WareHouse()
{
PartsInStock = new List<Part>();
AddStandardStockToCatalogus();
AddStockPartsToStock();
}
private void AddStandardStockToCatalogus()
{
Body body1 = new Body("Body7zk", 1, "Ebony", "Blue");
Body body2 = new Body("SuperXtrem368", 2, "Maple", "Red");
Neck neck1 = new Neck("Heavy-Slider", 3, "Juniper", true);
Neck neck2 = new Neck("SlickFingerBoard", 4, "Oak", false);
Fretboard fretboard1 = new Fretboard("PrettySlamFinger", 5, "Oak", false);
Fretboard fretboard2 = new Fretboard("GentleToucher", 6, "Ebony", true);
Pickups pickups1 = new Pickups("TubeScreamers", 7, Pickups.PickupType.P90);
Pickups pickups2 = new Pickups("BluesBrothers", 8, Pickups.PickupType.Humbucker);
Bridge bridge1 = new Bridge("MetalGearRipper", 9, false);
Bridge bridge2 = new Bridge("BridgeOfLove", 10, true);
Catalogus = new List<Part> { body1, body2, neck1, neck2, fretboard1, fretboard2, pickups1, pickups2, bridge1, bridge2};
}
private void AddStockPartsToStock()
{
foreach(Part p in Catalogus)
{
PartsInStock.Add(p);
}
}
The Delete() function in the same Warehouse class:
public int GetStock(Part neededPart)
{
if(neededPart == null)
{
throw new ArgumentNullException();
}
int numberInStock = 0;
foreach(Part p in PartsInStock)
{
if(p.Id == neededPart.Id)
{
numberInStock++;
}
}
return numberInStock;
}
public bool AddToStock(Part arrivedPart)
{
if(arrivedPart == null)
{
throw new ArgumentNullException();
}
if(FindByName(arrivedPart.Name).Name == arrivedPart.Name)
{
PartsInStock.Add(arrivedPart);
return true;
}
return false;
}
public bool RemovePart(Part usedPart)
{
if (GetStock(usedPart) >= 1)
{
PartsInStock.Remove(usedPart);
return true;
}
return false;
}
The unit test I run:
Neck neck1 = new Neck("Heavy-Slider", 3, "Juniper", true);
InjectorClass injector = new InjectorClass();
Assert.AreEqual(10, injector.WareHouse.PartsInStock.Count);
injector.WareHouse.RemovePart(neck1);
Assert.AreEqual(9, injector.WareHouse.PartsInStock.Count);
The output on both the assert functions is 10
Edit:
I have re-written my search and delete function to the following:
Search function:
public Part FindByNameStock(Part neededPart)
{
if (neededPart == null)
{
throw new ArgumentNullException();
}
foreach (Part p in PartsInStock)
{
if (p.Name == neededPart.Name)
{
return p;
}
}
return null;
}
Delete function:
public bool RemovePart(Part usedPart)
{
Part part = FindByNameStock(usedPart);
if (part != null)
{
PartsInStock.Remove(part);
return true;
}
return false;
}
The code now works and the unit test too.
This line PartsInStock.Remove(usedPart); will try to remove the following object
new Neck("Heavy-Slider", 3, "Juniper", true);
which is not in your list. The reason is that reference type equality is not based on the properties, but on the actual reference. Whenever you run new a new reference is created.
var neck1 = new Neck("Heavy-Slider", 3, "Juniper", true);
var neck2 = new Neck("Heavy-Slider", 3, "Juniper", true);
Console.WriteLine(neck1 == neck2); //This is false
Check the code here for an example: https://dotnetfiddle.net/7gAWID
Check here for the IEquatable implementation that will make your code work.
Two separate objects of Neck although having the same ID are different objects and hence the item is never removed.
Moreover, you can return the value of List.Remove as it also returns a bool if an item was actually removed. So if no item was removed, it returns false:
public bool RemovePart(Part usedPart)
=> GetStock(usedPart) >= 1 ?
PartsInStock.Remove(usedPart);
: false;
Use RemoveAll() to remove the item by ID. RemoveAll() returns an int representing the number of items removed:
public bool RemovePart(Part usedPart)
{
if (GetStock(usedPart) >= 1)
{
PartsInStock.RemoveAll(part => part.Id == usedPart.Id);
return true;
}
return false;
}
Also, since your RemovePart returns a bool. You can add a check to ensure if it was removed.
if (injector.WareHouse.RemovePart(neck1))
{
Assert.AreEqual(9, injector.WareHouse.PartsInStock.Count);
}
GetStock uses the Id of the part to find it, whereas PartsInStock.Remove tries to remove the part by reference - and the reference you have is different to the item in the collection you're trying to remove from.
public bool RemovePart(Part usedPart)
{
if (GetStock(usedPart) >= 1) // item is found here
{
PartsInStock.Remove(usedPart); // but usedPart is a different reference - nothing is removed
return true;
}
return false;
}

Comparison between three members of an object

Consider objects of the following type:
public class MyObject
{
// "defining" attributes
private string member1;
private string member2;
private string member3;
// other attributes
private string member4;
private string member5;
// ctor
public MyObject(){}
public bool compare(MyObject that)
{
// compare this object with another (that)
}
The compare() method should behave as follows. It only considers "defining" attributes. If they are all different between two objects, it should return false. If they are all the same, return false. In other cases, return true (if only one or two of them differ between the two objects).
The question is, do I have to resort to a huge if statement for this? Is there a "better" solution?
Instead of creating n number of strings, you can create property called List<string> DefiningAttributes and List<string> OtherAttributes.
Now add values to this lists where you want, for now I am doing it in constructor. Use Except() method to get difference from DefiningAttributes and OtherAttributes
Check below implementation
public class MyObject
{
// "defining" attributes
public List<string> DefiningAttributes { get; set; }
// other attributes
public List<string> OtherAttributes { get; set; }
public MyObject()
{
//I used constructor to assign values
DefiningAttributes = new List<string>() { "ABC", "PQR", "XYZ" };
OtherAttributes = new List<string>() { "ABC", "PQR", "Stackoverflow" };
}
public bool compare(MyObject that)
{
var difference = this.DefiningAttributes.Except(that.DefiningAttributes);
//Return false If they are all different between two objects OR if they are all same
if(difference.Count() == this.DefiningAttributes.Count() || !difference.Any())
return false;
//Otherwise return true
return true;
}
}
For more details, read Enumerable.Except method
I think this should do it
var comp1 = this.member1 == that.member1;
var comp2 = this.member2 == that.member2;
var comp3 = this.member3 == that.member3;
var comparisons = new List<string>() { comp2, comp3 };
return comparisons.Any(val => val != comp1 );
comp1, comp2 and comp3 will be bools. If any of those comparisons are not the same as the first comparison*, we know we have different results.
[*] You could use any reference point instead of the first comparison
Edit: Whoops, I thought this was a javascript question, but I then realized it was C#. I just changed my answer to use C# syntax, but the idea is the same. This requires the Linq extension method Any.
The following code should do the trick.
If you want to increase the number of defining properties you just edit the size of the array or swap it to a list.
It should iterate over them and when one does not mach return true.
If at the end none matches returns false.
public class MyObject
{
// "defining" attributes
public string[] definingAttributes = new string[3];
// other attributes
private string member4;
private string member5;
// ctor
public MyObject() { }
public bool compare(MyObject that)
{
bool? previousResult = null;
// compare this object with another (that)
for (int i = 0; i < definingAttributes.Length; i++)
{
if (previousResult == null)
{
previousResult = definingAttributes[i] == that.definingAttributes[i];
}
if (definingAttributes[i] != that.definingAttributes[i])
{
if (previousResult != (definingAttributes[i] == that.definingAttributes[i]))
{
return true;
}
}
}
return false;
}
}

Assert Equal two list of objects UnitTesting c#

I'm currently doing some unit testing of a copy function and I need to compare the elements of the objects between the old list, and the newly copied list.
It works fine, but I was wondering if I can do it in a way that doesn't involve a for loop.
Here is my object:
new NaturePointObject
{
SId = 1,
Name = "Test",
Category = NaturePointCategory.Category1,
CreatorType = CreatorTypeEnum.1,
NaturR = NaturR.Bn,
Description = "Test",
Kumulation = Kumulation.EnEjendom,
Id = 1
}
My old list contains "NaturePointObject" and is called naturPointList, and it will be copied to a list called newNaturePointList.
Here is how I Assert to know if it copied succesfully:
Assert.AreEqual(naturPointList.Count,newNaturePointList.Count);
for (var i = 0; i < newNatureList.Count; i++)
{
Assert.AreEqual(naturPointList[i].Category, newNaturePointList[i].Category);
Assert.AreEqual(naturPointList[i].Description, newNaturePointList[i].Description);
Assert.AreEqual(naturPointList[i].Kumulation, newNaturePointList[i].Kumulation);
Assert.AreEqual(naturPointList[i].Name, newNaturePointList[i].Name);
Assert.AreEqual(naturPointList[i].CreatorType, newNaturePointList[i].CreatorType);
Assert.AreEqual(naturPointList[i].NaturR, newNaturePointList[i].NaturR);
Assert.AreNotEqual(naturPointList[i].SId, newNaturePointList[i].SId);
}
As you can see not all elements of the object must be equal. And I don't care about the "Id" of the object.
Is there a shorter way to do this, than run a for loop?
Probably you want to use CollectionAssert:
CollectionAssert.AreEqual(naturPointList, newNaturePointList, NaturePointObject.CategoryCreatorTypeComparer);
The only thing you need to take in mind is that you need to implement IComparer, to use in the Assert method:
public class NaturePointObject
{
private static readonly Comparer<NaturePointObject> CategoryCreatorTypeComparerInstance = new CategoryCreatorTypeRelationalComparer();
private sealed class CategoryCreatorTypeRelationalComparer : Comparer<NaturePointObject>
{
public override int Compare(NaturePointObject x, NaturePointObject y)
{
// compare fields which makes sense
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
var categoryComparison = string.Compare(x.Category, y.Category, StringComparison.Ordinal);
if (categoryComparison != 0) return categoryComparison;
return string.Compare(x.CreatorType, y.CreatorType, StringComparison.Ordinal);
}
}
public static Comparer<NaturePointObject> CategoryCreatorTypeComparer
{
get
{
return CategoryCreatorTypeComparerInstance;
}
}
public int SId { get; set; }
public string Category { get; set; }
//other properties
public string CreatorType { get; set; }
}
You can try
Assert.IsTrue(naturPointList.SequenceEqual(newNaturePointList));
If you want to ignore the Id, you can create other classes (without Ids).
Later edit: you could overwrite the Equals method and ignore the Id.

Using Linq to determine if there is more than 2 distinct items in a class

I have a class that contains a list of another class which has a property that I want to check if it has more than one distinct value.
e.g
public class BasketModel
{
public BasketModel()
{
BasketOrderLines = new List<BasketOrderLine>();
}
.
.
.
public class BasketOrderLine
{
public int OrderLineId { get; set; }
public string ImageUrl { get; set; }
public string ProductType { get; set; }
.
.
Given a basket model object I want to find out if there are more than one distinct value in the ProductType.
e.g If all Product Types are "A" then that would be false, if 3 products are of type "A" and one is of type "B" then this would be true.
Cheers
Macca
Your title: "more than two distinct", your question body: "more than one distinct"
If the title is a typo:
bool notDistinctTypes = theBasket.BasketOrderLine
.Select(o => o.ProductType)
.Distinct()
.Skip(1)
.Any();
This doesn't need to enumerate all items to find out if there is more than one ProductType.
// Does this basket contains three or more types
public bool HasSeveralTypes(BasketModel basket)
{
if (basket == null)
return false;
int differentTypes = basket.BasketOrderLines
.Select(l => l.ProductType)
.Distinct()
.Count();
return (differentTypes > 2);
}
Something like this :
Public bool CheckDistinct (){
var res = basketOrderLines.Select(o => o.ProductType).Distinct ().Count ();
return res > 1;
}
There are a few ways to do this, here's one:
public class BasketModel
{
public BasketModel()
{
BasketOrderLines = new List<BasketOrderLine>();
}
public bool HasMulitpleDistinctProducts
{
get
{
if (!BasketOrderLines.Any())
{
return true; // or false?
}
return BasketOrderLines.Select(b => b.ProductType).Distinct().Count() > 1;
}
}
}
Here is a type extension you can call directly from your list. The pros of this code is to be adaptable to any type implementing IEquals and not only string + kick to use from your code.
The code :
public static class Tools
{
public static bool fDistinctProductType(this List<BasketOrderLine> lstToAnalyse)
{
BasketOrderLine ProductTypeA = lstToAnalyse.FirstOrDefault();
if (ProductTypeA == null) // It's null when lstToAnalyse is empty
return false;
BasketOrderLine ProductTypeB = lstToAnalyse.Where(b => b.ProductType.Equals(ProductTypeA.ProductType)).FirstOrDefault();
if (ProductTypeB == null) // It's null when it doesn't exists a distinct ProductType
return false;
return true;
}
}
How to call:
List<BasketOrderLine> lst = new List<BasketOrderLine>();
// Add element to list
if (lst.fDistinctProductType())
{
// DO Something
}

Categories