I try to subtract 2 lists like below code, assignUsers
has got 3 records and assignedUsers
has got 2 rows. After Except
method I still get 3 rows, although I should get 1 record because 2 rows in assignedUsers
is similar to assignUsers
var users = accountApp.GetUsersByAccountId(context.GetUserData().AccountId);
List<AssignUserViewModel> assignUsers = Mapper.Map<List<AssignUserViewModel>>(users).ToList();
var mailUsers = mailApp.GetMailAssignedByMailId(id).Select(m => new { m.UserId, m.User.Name }).ToList();
List<AssignUserViewModel> assignedUsers = mailUsers.Select(Mapper.DynamicMap<AssignUserViewModel>).ToList();
assignUsers = assignUsers.Except(assignedUsers).ToList();
Get the difference between two arrays using the Except() method. The following are the two arrays. int[] arr = { 9, 12, 15, 20, 35, 40, 55, 67, 88, 92 }; int[] arr2 = { 20, 35 }; To get the difference, use Except() method that returns the first list, except the elements in the second list.
The Except() method requires two collections. It returns a new collection with elements from the first collection which do not exist in the second collection (parameter collection). Except extension method doesn't return the correct result for the collection of complex types.
What is LINQ Except in C#? The LINQ Except Method in C# is used to return the elements which are present in the first data source but not in the second data source. There are two overloaded versions available for the LINQ Except Method as shown below.
In order to make Except
method working as expected, the class AssignUserViewModel
must have GetHashCode
and Equals
methods correctly overridden.
For example, if AssignUserViewModel
objects are uniquely defined by their Id
, you should define the class in this way:
class AssignUserViewModel
{
// other methods...
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
public override bool Equals(object obj)
{
if (!(obj is AssignUserViewModel))
throw new ArgumentException("obj is not an AssignUserViewModel");
var usr = obj as AssignUserViewModel;
if (usr == null)
return false;
return this.Id.Equals(usr.Id);
}
}
Otherwise, if you can't/don't want to change the class implementation, you can implement an IEqualityComparer<>
and pass it to the Except
method, e.g. :
class AssignUserViewModelEqualityComparer : IEqualityComparer<AssignUserViewModel>
{
public bool Equals(AssignUserViewModel x, AssignUserViewModel y)
{
if (object.ReferenceEquals(x, y))
return true;
if(x == null || y == null)
return false;
return x.Id.Equals(y.Id);
}
public int GetHashCode(AssignUserViewModel obj)
{
return obj.Id.GetHashCode();
}
}
then your last line would become:
assignUsers = assignUsers.Except(assignedUsers, new AssignUserViewModelEqualityComparer()).ToList();
Why this happening?
When you use Set Operations (Distinct, Except, Intersect, Union) Linq need to compare sequence(s) elements for equality. By default Linq uses Object.Equals and Object.GetHashCode methods to compare elements. If these methods are not overridden in your type, then base class's implementation is used, which compare objects by reference equality. Default implementation guarantees that two objects that are the same reference have the same hash code (thus considered equal). This is your case. Mapper
class creates new instances of AssignUserViewModel
objects, which have different references, and cannot be treated as equal (even if all field values are the same).
So, what could we do with this?
Override Equals
and GetHashCode
methods in your class. It's up to you how you will treat objects equality - all fields, or just identity. Linq will use your methods to compare elements.
Provide your own comparer (this is usually case when you cannot modify your object and override Equals
and GetHashCode
. Yes, all Linq Set Operations have two overloads - one which uses default comparer, and other, which accepts yours IEqualityComparer<T>
.
Use anonymous types. All anonymous types already have generated methods Equals
and GetHashCode
, which use comparison of all properties to determine if objects are equal. In this case you don't need neither to modify your type nor to create comparer.
Thus you already have samples of first two approaches, here is last one:
var assignUsers = accountApp.GetUsersByAccountId(context.GetUserData().AccountId)
.Select(u => new { u.UserId, u.Name });
var assignedUsers = mailApp.GetMailAssignedByMailId(id)
.Select(m => new { m.UserId, m.User.Name });
var assignUsers = assignUser.Except(assignedUsers);
// do not map until here
List<AssignUserViewModel> result =
assignUsers.Select(Mapper.DynamicMap<AssignUserViewModel>).ToList();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With