I have these two lists result and resultNew:
data.AddMapping<Employee>(x => x.Name, "Name");
data.AddMapping<Employee>(x => x.Code, "Code");
data.AddMapping<Employee>(x => x.WorkingStatus, "Working Status");
var result = (from x in data.Worksheet<Employee>("Tradesmen")
select x).ToList();
dataNew.AddMapping<Employee>(x => x.Name, "Name");
dataNew.AddMapping<Employee>(x => x.Code, "Code");
dataNew.AddMapping<Employee>(x => x.WorkingStatus, "Working Status");
var resultNew = (from x in dataNew.Worksheet<Employee>("On Leave")
select x).ToList();
where Employee is a simple c# code that contains code, name and workingStatus fields
I want to take the data which its code is the resultNew and not in the result
I tried this:
var newEmployees = resultNew.Except(Code = result.Select(s => s.Code)).ToList();
but I got syntax error:
System.Collections.Generic.List' does not contain a definition for 'Except' and the best extension method overload 'System.Linq.Enumerable.Except(System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerable)' has some invalid arguments
You can create a HashSet for Code of new employees and then use it like:
HashSet<string> resultCodes = new HashSet<string>(result.Select(r => r.Code));
List<Employee> newEmployees = resultNew.Where(r => !resultCodes.Contains(r.Code))
.ToList();
You can also override Equals and GetHashCode for your class Employee base on property Code and then you can use Except like:
class Employee
{
protected bool Equals(Employee other)
{
return string.Equals(Code, other.Code);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Employee) obj);
}
public override int GetHashCode()
{
return (Code != null ? Code.GetHashCode() : 0);
}
public string Name { get; set; }
public string Code { get; set; }
public string WorkingStatus { get; set; }
}
and then:
var newEmployees = resultnew.Except(result).ToList();
Remember the above implementation of Equals and GetHashCode only considers Code property. See this question How do you implement GetHashCode for structure with two string, when both strings are interchangeable
Related
I am writing a unit test for a manual mapper. It maps an object to two different classes but have common properties. how to compare if their properties are equal in fluent assertion?
This is what I tried
var domain = new Domain.ConsentDefinition()
{
SomeProperty = 1,
ListOfFirstDTO = new List<FirstDTO>()
{
new FirstDTO()
{
Name = "Label",
Age = 18,
}
},
SomeOtherProperty = "one"
}
ef = domain.ToEF();
domain.SomeProperty.Should().Be(ef.SomeProperty);
domain.SomeOtherProperty.Should().Be(ef.SomeOtherProperty);
domain.ListFirstDTO.Should().Equal(ef.ListOfSecondDTO); // This is NOT working
classes
public class FirstDTO
{
public string Name {get;set;}
public int Age {get;set;}
}
public class SecondDTO
{
public string Name {get;set;}
public int Age {get;set;}
public string Email {get;set;}
}
Override firstDTO's equals so you compare values instead of references:
public override bool Equals(object obj)
{
if (obj == null || !(obj is FirstDTO) || !(obj is SecondDTO))
{
return false;
}
if(obj is SecondDTO){
return (this.Name == ((SecondDTO)obj).Name)
&& (this.Age == ((SecondDTO)obj).Age)
}
// if obj is instance of FirstDTO check the rest of fields...
}
and run again
domain.ListFirstDTO.Should().Equal(ef.ListOfSecondDTO); // This is NOT working
Another more elegant solution with no need of overriding equals would be
domain.ListFirstDTO.Select(c => c.Name).Should().Equal(ef.ListOfSecondDTO.Select(c => c.Name);
domain.ListFirstDTO.Select(c => c.Age).Should().Equal(ef.ListOfSecondDTO.Select(c => c.Age);
fluentassertion/collections
domain.Should().BeEquivalentTo(new
{
SomeProperty = ef.SomeProperty,
SomeOtherProperty = ef.SomeOtherProperty,
ListFirstDTO = ef.ListOfSecondDTO
});
or
domain.Should().BeEquivalentTo(ef, options => options
.Including(x => x.SomeProperty)
.Including(x => x.SomeOtherProperty)
.Including(x => x.ListOfSecondDTO));
By default, FA will compare two collections by ignoring the order of the items in the collection. Use WithStrictOrdering to control that.
If the second DTO implements Equals, then FA will use that. You can override that by using the ComparingByMembers<T> option.
This question already has answers here:
LINQ's Distinct() on a particular property
(23 answers)
Closed 20 days ago.
class Program
{
static void Main(string[] args)
{
List<Book> books = new List<Book>
{
new Book
{
Name="C# in Depth",
Authors = new List<Author>
{
new Author
{
FirstName = "Jon", LastName="Skeet"
},
new Author
{
FirstName = "Jon", LastName="Skeet"
},
}
},
new Book
{
Name="LINQ in Action",
Authors = new List<Author>
{
new Author
{
FirstName = "Fabrice", LastName="Marguerie"
},
new Author
{
FirstName = "Steve", LastName="Eichert"
},
new Author
{
FirstName = "Jim", LastName="Wooley"
},
}
},
};
var temp = books.SelectMany(book => book.Authors).Distinct();
foreach (var author in temp)
{
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Console.Read();
}
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
return true;
//if (obj.GetType() != typeof(Author)) return false;
//else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
}
}
This is based on an example in "LINQ in Action". Listing 4.16.
This prints Jon Skeet twice. Why? I have even tried overriding Equals method in Author class. Still Distinct does not seem to work. What am I missing?
Edit:
I have added == and != operator overload too. Still no help.
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
LINQ Distinct is not that smart when it comes to custom objects.
All it does is look at your list and see that it has two different objects (it doesn't care that they have the same values for the member fields).
One workaround is to implement the IEquatable interface as shown here.
If you modify your Author class like so it should work.
public class Author : IEquatable<Author>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
public override int GetHashCode()
{
int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
int hashLastName = LastName == null ? 0 : LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}
Try it as DotNetFiddle
The Distinct() method checks reference equality for reference types. This means it is looking for literally the same object duplicated, not different objects which contain the same values.
There is an overload which takes an IEqualityComparer, so you can specify different logic for determining whether a given object equals another.
If you want Author to normally behave like a normal object (i.e. only reference equality), but for the purposes of Distinct check equality by name values, use an IEqualityComparer. If you always want Author objects to be compared based on the name values, then override GetHashCode and Equals, or implement IEquatable.
The two members on the IEqualityComparer interface are Equals and GetHashCode. Your logic for determining whether two Author objects are equal appears to be if the First and Last name strings are the same.
public class AuthorEquals : IEqualityComparer<Author>
{
public bool Equals(Author left, Author right)
{
if((object)left == null && (object)right == null)
{
return true;
}
if((object)left == null || (object)right == null)
{
return false;
}
return left.FirstName == right.FirstName && left.LastName == right.LastName;
}
public int GetHashCode(Author author)
{
return (author.FirstName + author.LastName).GetHashCode();
}
}
Another solution without implementing IEquatable, Equals and GetHashCode is to use the LINQs GroupBy method and to select the first item from the IGrouping.
var temp = books.SelectMany(book => book.Authors)
.GroupBy (y => y.FirstName + y.LastName )
.Select (y => y.First ());
foreach (var author in temp){
Console.WriteLine(author.FirstName + " " + author.LastName);
}
There is one more way to get distinct values from list of user defined data type:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
Surely, it will give distinct set of data
Distinct() performs the default equality comparison on objects in the enumerable. If you have not overridden Equals() and GetHashCode(), then it uses the default implementation on object, which compares references.
The simple solution is to add a correct implementation of Equals() and GetHashCode() to all classes which participate in the object graph you are comparing (ie Book and Author).
The IEqualityComparer interface is a convenience that allows you to implement Equals() and GetHashCode() in a separate class when you don't have access to the internals of the classes you need to compare, or if you are using a different method of comparison.
You've overriden Equals(), but make sure you also override GetHashCode()
The Above answers are wrong!!!
Distinct as stated on MSDN returns the default Equator which as stated The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T
Which means as long as you overide Equals you are fine.
The reason you're code is not working is because you check firstname==lastname.
see https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx and https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx
You can achieve this several ways:
1. You may to implement the IEquatable interface as shown Enumerable.Distinct Method or you can see #skalb's answer at this post
2. If your object has not unique key, You can use GroupBy method for achive distinct object list, that you must group object's all properties and after select first object.
For example like as below and working for me:
var distinctList= list.GroupBy(x => new {
Name= x.Name,
Phone= x.Phone,
Email= x.Email,
Country= x.Country
}, y=> y)
.Select(x => x.First())
.ToList()
MyObject class is like as below:
public class MyClass{
public string Name{get;set;}
public string Phone{get;set;}
public string Email{get;set;}
public string Country{get;set;}
}
3. If your object's has unique key, you can only use the it in group by.
For example my object's unique key is Id.
var distinctList= list.GroupBy(x =>x.Id)
.Select(x => x.First())
.ToList()
You can use extension method on list which checks uniqueness based on computed Hash.
You can also change extension method to support IEnumerable.
Example:
public class Employee{
public string Name{get;set;}
public int Age{get;set;}
}
List<Employee> employees = new List<Employee>();
employees.Add(new Employee{Name="XYZ", Age=30});
employees.Add(new Employee{Name="XYZ", Age=30});
employees = employees.Unique(); //Gives list which contains unique objects.
Extension Method:
public static class LinqExtension
{
public static List<T> Unique<T>(this List<T> input)
{
HashSet<string> uniqueHashes = new HashSet<string>();
List<T> uniqueItems = new List<T>();
input.ForEach(x =>
{
string hashCode = ComputeHash(x);
if (uniqueHashes.Contains(hashCode))
{
return;
}
uniqueHashes.Add(hashCode);
uniqueItems.Add(x);
});
return uniqueItems;
}
private static string ComputeHash<T>(T entity)
{
System.Security.Cryptography.SHA1CryptoServiceProvider sh = new System.Security.Cryptography.SHA1CryptoServiceProvider();
string input = JsonConvert.SerializeObject(entity);
byte[] originalBytes = ASCIIEncoding.Default.GetBytes(input);
byte[] encodedBytes = sh.ComputeHash(originalBytes);
return BitConverter.ToString(encodedBytes).Replace("-", "");
}
The Equal operator in below code is incorrect.
Old
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
NEW
public override bool Equals(Object obj)
{
var other = obj as Author;
if (other is null)
{
return false;
}
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
Instead of
var temp = books.SelectMany(book => book.Authors).Distinct();
Do
var temp = books.SelectMany(book => book.Authors).DistinctBy(f => f.Property);
I currently have an IEnumerable<MyObject> where MyObject has the properties String Name and long Value.
If i was to have within the Enumerable, 10 instances of MyObject, each with a different name and value, with the exception of one having the same name as the other.
Does .NET (or LINQ) have a built in method which will allow me to find the duplicate, and if possible, merge the Value property so that there ends up being only 9 elements within the enumerable, each with a distinct Name and the one that had a duplicate has the Value that is equal to the sum of its self and the duplicate.
So far i have found that the only way to iterate over the entire IEnumerable and look for the duplicates and generate a new IEnumerable of unique items, but this seems untidy and slow.
You can group items by name and project results to 'merged' objects:
objects.GroupBy(o => o.Name)
.Select(g => new MyObject { Name = g.Key, Value = g.Sum(o => o.Value) });
UPDATE: Another option, if new MyObject instantiation is undesired (e.g. you have many properties in this class, or you should preserver references) then you can use aggregation with first item in group as accumulator:
objects.GroupBy(o => o.Name)
.Select(g => g.Skip(1).Aggregate(
g.First(), (a, o) => { a.Value += o.Value; return a; }));
list.GroupBy(e => e.Name).Select(group => new MyObject
{
Name = group.Key,
Value = group.Sum(e => e.Value)
}
)
Update:
Another variant:
list.GroupBy(
e => e.Name,
e => e,
(name, group) => group.Aggregate((result, e) =>
{
result.Value += e.Value;
return result;
}
)
)
I dont know a single method solution but what about:
set.GroupBy(g=>g.Name).Select(g=> new MyObject{Name=g.Key, Value=g.Sum(i=>i.Value)});
Implement interface IEquatable and use Ditinct method. As follow:
internal class Program
{
private static void Main(string[] args)
{
var items = new List<MyClass>
{
new MyClass
{
Name = "Name1",
Value = 50
},
new MyClass
{
Name = "Name2",
Value = 20
},
new MyClass
{
Name = "Name3",
Value = 50
}
};
var distinct = items.Distinct().ToList();
}
}
internal class MyClass : **IEquatable<MyClass>**
{
public String Name { get; set; }
public int Value { get; set; }
**public bool Equals(MyClass other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return this.Value == other.Value;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != this.GetType())
return false;
return this.Equals((MyClass)obj);
}
public override int GetHashCode()
{
return this.Value;
}
public static bool operator ==(MyClass left, MyClass right)
{
return Equals(left, right);
}
public static bool operator !=(MyClass left, MyClass right)
{
return !Equals(left, right);
}**
}
This question already has answers here:
How can I maintain type when using LINQ .Select in C#?
(4 answers)
Closed 9 years ago.
The following code still does not return a DISTINCT result set. The equivalent SQL I am trying to accomplish is SELECT DISTINCT LEFT(Fac_Name, 6) AS ID, LEFT(Fac_Name, 3) AS Fac_Name
public List<Facility> GetFacilities() {
var facilities = new List<Facility>();
facilities = _facilityRepository.GetAll().ToList();
var facReturnList =
facilities.Where(x => x.Fac_Name = "Something")
.OrderBy(x => x.Fac_Name).ToList();
var facReturnList2 =
facReturnList.Select(x =>
new Facility { ID = x.Fac_Name.Substring(0, 6),
Fac_Name = x.Fac_Name.Substring(0, 3) })
.Distinct().ToList();
return facReturnList2;
}
The problem you have is that you're creating distinct reference values (which will return different hashcodes), even if the properties inside each reference are equal, the actual references themselves are distinct.
// fac1 and fac2 are the same reference, fac3 is a different reference.
var fac1 = new Facility { ID = "0", Fac_Name = "Hello" };
var fac2 = fac1;
var fac3 = new Facility { ID = "0", Fac_Name = "Hello" };
var facs = new List<Facility>() { fac1, fac2, fac3 };
foreach (var fac in facs.Distinct())
Console.WriteLine("Id: {0} | Name: {1}", fac.ID, fac.Fac_Name);
// OUTPUT
// Id: 0 | Name: Hello (NOTE: This is the value of fac1/fac2)
// Id: 0 | Name: Hello (This is the value of fac3)
To solve your dilemma, you should either:
Override the Object.GetHashCode() and the Object.Equals(Object) methods. Note that Distinct() ultimately uses the GetHashCode() to determine if something is distinct, but Equals(Object) and GetHashCode() should be overridden together.
Guidelines for Overloading Equals() and Operator ==
public class Facility
{
public string ID { get; set; }
public string Fac_Name { get; set; }
// This is just a rough example.
public override bool Equals(Object obj)
{
var fac = obj as Facility;
if (fac == null) return false;
if (Object.ReferenceEquals(this, fac)) return true;
return (this.ID == fac.ID) && (this.Fac_Name == fac.Fac_Name);
}
public override int GetHashCode()
{
var hash = 13;
if (!String.IsNullOrEmpty(this.ID))
hash ^= ID.GetHashCode();
if (!String.IsNullOrEmpty(this.Fac_Name))
hash ^= Fac_Name.GetHashCode();
return hash;
}
}
Provide a custom IEqualityComparer<T>.
public class FacilityEqualityComparer : IEqualityComparer<Facility>
{
public bool Equals(Facility x, Facility y)
{
return (x.ID == y.ID) && (x.Fac_Name == y.Fac_Name);
}
public int GetHashCode(Facility fac)
{
var hash = 13;
if (!String.IsNullOrEmpty(this.ID))
hash ^= ID.GetHashCode();
if (!String.IsNullOrEmpty(this.Fac_Name))
hash ^= Fac_Name.GetHashCode();
return hash;
}
}
var facReturnList2 =
facReturnList.Select(x =>
new Facility { ID = x.Fac_Name.Substring(0, 6),
Fac_Name = x.Fac_Name.Substring(0, 3) })
.Distinct(new FacilityEqualityComparer()).ToList();
Also, some other things to note:
You're naming does not follow guidelines. Don't use underscores in property names, and ID should be Id.
Whichever way you decide to go with, you should look into using String.Equals(...) and specify a StringComparison value. I just used == equality comparison on strings to keep the post short and readable.
So the problem is that the Enumerable.Distinct method uses the default equality comparer - which is comparing hash codes - so it will be a distinct list regardless of the properties values. Build an equality comparer for that type:
public class FacilityEqualityComparer : IEqualityComparer<Facility>
{
public bool Equals(Facility fac1, Facility fac2)
{
return fac1.ID.Equals(fac2.ID) && fac1.Fac_Name.Equals(fac2.Fac_Name);
}
public int GetHashCode(Facility fac)
{
string hCode = fac.ID + fac.Fac_Name;
return hCode.GetHashCode();
}
}
and then when you use it, call it like this:
var facReturnList2 =
facReturnList.Select(x =>
new Facility { ID = x.Fac_Name.Substring(0, 6),
Fac_Name = x.Fac_Name.Substring(0, 3) })
.Distinct(new FacilityEqualityComparer()).ToList();
return facReturnList2;
Distinct uses the default equality comparer to check for equality. This means it's looking for reference equality, which obviously won't be there in your case.
So you'll either need to use a custom IEqualityComparer (see the overload for Distinct(), or you can replicate the functionality of Distinct() with a GroupBy() and a First():
facReturnList.Select(x =>
new Facility { ID = x.Fac_Name.Substring(0, 6),
Fac_Name = x.Fac_Name.Substring(0, 3)
})
.GroupBy(x => new{x.ID, x.Fac_Name})
.Select(y => y.First())
.ToList();
You could also Override the Equals method in your Facility class:
public override bool Equals(System.Object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
Facility objAsFacility = obj as Facility;
return Equals(objAsFacility);
}
protected bool Equals(Facility other)
{
if (other.Fac_Name == this.Fac_Name)
return true;
else return false;
}
public override int GetHashCode()
{
return this.Fac_Name.GetHashCode();
//Or you might even want to this:
//return (this.ID + this.Fac_Name).GetHashCode();
}
I'd probably go with the overriding equality operator method.
This question already has answers here:
LINQ's Distinct() on a particular property
(23 answers)
Closed 23 days ago.
class Program
{
static void Main(string[] args)
{
List<Book> books = new List<Book>
{
new Book
{
Name="C# in Depth",
Authors = new List<Author>
{
new Author
{
FirstName = "Jon", LastName="Skeet"
},
new Author
{
FirstName = "Jon", LastName="Skeet"
},
}
},
new Book
{
Name="LINQ in Action",
Authors = new List<Author>
{
new Author
{
FirstName = "Fabrice", LastName="Marguerie"
},
new Author
{
FirstName = "Steve", LastName="Eichert"
},
new Author
{
FirstName = "Jim", LastName="Wooley"
},
}
},
};
var temp = books.SelectMany(book => book.Authors).Distinct();
foreach (var author in temp)
{
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Console.Read();
}
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
return true;
//if (obj.GetType() != typeof(Author)) return false;
//else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
}
}
This is based on an example in "LINQ in Action". Listing 4.16.
This prints Jon Skeet twice. Why? I have even tried overriding Equals method in Author class. Still Distinct does not seem to work. What am I missing?
Edit:
I have added == and != operator overload too. Still no help.
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
LINQ Distinct is not that smart when it comes to custom objects.
All it does is look at your list and see that it has two different objects (it doesn't care that they have the same values for the member fields).
One workaround is to implement the IEquatable interface as shown here.
If you modify your Author class like so it should work.
public class Author : IEquatable<Author>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
public override int GetHashCode()
{
int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
int hashLastName = LastName == null ? 0 : LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}
Try it as DotNetFiddle
The Distinct() method checks reference equality for reference types. This means it is looking for literally the same object duplicated, not different objects which contain the same values.
There is an overload which takes an IEqualityComparer, so you can specify different logic for determining whether a given object equals another.
If you want Author to normally behave like a normal object (i.e. only reference equality), but for the purposes of Distinct check equality by name values, use an IEqualityComparer. If you always want Author objects to be compared based on the name values, then override GetHashCode and Equals, or implement IEquatable.
The two members on the IEqualityComparer interface are Equals and GetHashCode. Your logic for determining whether two Author objects are equal appears to be if the First and Last name strings are the same.
public class AuthorEquals : IEqualityComparer<Author>
{
public bool Equals(Author left, Author right)
{
if((object)left == null && (object)right == null)
{
return true;
}
if((object)left == null || (object)right == null)
{
return false;
}
return left.FirstName == right.FirstName && left.LastName == right.LastName;
}
public int GetHashCode(Author author)
{
return (author.FirstName + author.LastName).GetHashCode();
}
}
Another solution without implementing IEquatable, Equals and GetHashCode is to use the LINQs GroupBy method and to select the first item from the IGrouping.
var temp = books.SelectMany(book => book.Authors)
.GroupBy (y => y.FirstName + y.LastName )
.Select (y => y.First ());
foreach (var author in temp){
Console.WriteLine(author.FirstName + " " + author.LastName);
}
There is one more way to get distinct values from list of user defined data type:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
Surely, it will give distinct set of data
Distinct() performs the default equality comparison on objects in the enumerable. If you have not overridden Equals() and GetHashCode(), then it uses the default implementation on object, which compares references.
The simple solution is to add a correct implementation of Equals() and GetHashCode() to all classes which participate in the object graph you are comparing (ie Book and Author).
The IEqualityComparer interface is a convenience that allows you to implement Equals() and GetHashCode() in a separate class when you don't have access to the internals of the classes you need to compare, or if you are using a different method of comparison.
You've overriden Equals(), but make sure you also override GetHashCode()
The Above answers are wrong!!!
Distinct as stated on MSDN returns the default Equator which as stated The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T
Which means as long as you overide Equals you are fine.
The reason you're code is not working is because you check firstname==lastname.
see https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx and https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx
You can achieve this several ways:
1. You may to implement the IEquatable interface as shown Enumerable.Distinct Method or you can see #skalb's answer at this post
2. If your object has not unique key, You can use GroupBy method for achive distinct object list, that you must group object's all properties and after select first object.
For example like as below and working for me:
var distinctList= list.GroupBy(x => new {
Name= x.Name,
Phone= x.Phone,
Email= x.Email,
Country= x.Country
}, y=> y)
.Select(x => x.First())
.ToList()
MyObject class is like as below:
public class MyClass{
public string Name{get;set;}
public string Phone{get;set;}
public string Email{get;set;}
public string Country{get;set;}
}
3. If your object's has unique key, you can only use the it in group by.
For example my object's unique key is Id.
var distinctList= list.GroupBy(x =>x.Id)
.Select(x => x.First())
.ToList()
You can use extension method on list which checks uniqueness based on computed Hash.
You can also change extension method to support IEnumerable.
Example:
public class Employee{
public string Name{get;set;}
public int Age{get;set;}
}
List<Employee> employees = new List<Employee>();
employees.Add(new Employee{Name="XYZ", Age=30});
employees.Add(new Employee{Name="XYZ", Age=30});
employees = employees.Unique(); //Gives list which contains unique objects.
Extension Method:
public static class LinqExtension
{
public static List<T> Unique<T>(this List<T> input)
{
HashSet<string> uniqueHashes = new HashSet<string>();
List<T> uniqueItems = new List<T>();
input.ForEach(x =>
{
string hashCode = ComputeHash(x);
if (uniqueHashes.Contains(hashCode))
{
return;
}
uniqueHashes.Add(hashCode);
uniqueItems.Add(x);
});
return uniqueItems;
}
private static string ComputeHash<T>(T entity)
{
System.Security.Cryptography.SHA1CryptoServiceProvider sh = new System.Security.Cryptography.SHA1CryptoServiceProvider();
string input = JsonConvert.SerializeObject(entity);
byte[] originalBytes = ASCIIEncoding.Default.GetBytes(input);
byte[] encodedBytes = sh.ComputeHash(originalBytes);
return BitConverter.ToString(encodedBytes).Replace("-", "");
}
The Equal operator in below code is incorrect.
Old
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
NEW
public override bool Equals(Object obj)
{
var other = obj as Author;
if (other is null)
{
return false;
}
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
Instead of
var temp = books.SelectMany(book => book.Authors).Distinct();
Do
var temp = books.SelectMany(book => book.Authors).DistinctBy(f => f.Property);