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);
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.
Hi I have the following lists:
var CustomerName = new List<Customer>();
var DummyData = new List<Customer>();
How can I quickly check that DummyData is contained inside of CustomerName? Also performance is key as these lists might contain thousands of values.
Brute Force Method
Use linq all method against the DummyData variable O(N*K)
// If you override Equals and GetHashCode or are comparing by reference
DummyData.All(a=>CustomerName.Contains(a))
//If you compare by property
DummyData.All(a=>
CustomerName.Any(b=>
a.FirstName==b.FirstName &&
a.LastName == b.LastName
//repeat to include checks for all properties
)
);
Using a HashSet
Put your results into a hashset and use linq's All method again checking if hashset contains items, takes N steps to build hashset and K steps to check, complexity is O(N+K)
var hs = new HashSet<Customer>(CustomerName);
DummyData.All(a=>hs.Contains(a));
You will need to override Equals And GetHashCode
If you haven't overriden these two yet you'll need to unless you want to compare properties and this prevents you from using the hash set method as well
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
var customer = obj as Customer;
return customer != null && Equals(customer);
}
protected bool Equals(Customer other)
{
return string.Equals(FirstName, other.FirstName) && string.Equals(LastName, other.LastName);
}
public override int GetHashCode()
{
unchecked
{
return ((FirstName?.GetHashCode() ?? 0)*397) ^ (LastName?.GetHashCode() ?? 0);
}
}
}
I have implemented a comparison class for my custom class, so that I can use Intersect on two lists (StudentList1 and StudentList2). However, when I run the following code, I don't get any results.
Student:
class CompareStudent : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
if (x.Age == y.Age && x.StudentName.ToLower() == y.StudentName.ToLower())
return true;
return false;
}
public int GetHashCode(Student obj)
{
return obj.GetHashCode();
}
}
class Student
{
public int StudentId{set;get;}
public string StudentName{set;get;}
public int Age{get;set;}
public int StandardId { get; set; }
}
Main:
IList<Student> StudentList1 = new List<Student>{
new Student{StudentId=1,StudentName="faisal",Age=29,StandardId=1},
new Student{StudentId=2,StudentName="qais",Age=19,StandardId=2},
new Student{StudentId=3,StudentName="ali",Age=19}
};
IList<Student> StudentList2 = new List<Student>{
new Student{StudentId=1,StudentName="faisal",Age=29,StandardId=1},
new Student{StudentId=2,StudentName="qais",Age=19,StandardId=2},
};
var NewStdList = StudentList1.Intersect(StudentList2, new CompareStudent());
Console.ReadLine();
The problem is within your GetHashCode() method, change it to:
public int GetHashCode(Student obj)
{
return obj.StudentId ^ obj.Age ^ obj.StandardId ^ obj.StudentName.Length;
}
In your current code, Equals is not called as the current GetHashCode() returns two different integers, so no point in calling Equals.
If GetHashCode of the first object is different than the second, the objects are not equal, if the result is the same, Equals is being called.
The GetHashCode I've written above is not ultimate, see What is the best algorithm for an overridden System.Object.GetHashCode? for more details on how to implement GetHashCode.
GetHashCode() is not (and cannot be) collision free, which is why the Equals method is required in the first place.
You are calling GetHashCode() on the base object, which will return a different value for the different references. I would implement it like this:
public override int GetHashCode(Student obj)
{
unchecked
{
return obj.StudentName.GetHashCode() + obj.Age.GetHashCode();
}
}
I have several classes in my c# application.
I have a plant class with a constructor that takes a name and a weight.
Then I have a Fruit class that inherits plant and adds the number of seeds attribute.
Also I have a Veg class that inherits from plant and adds the savoryLevel attribute.
fruit and veg can be added to their lists by the user.
I have overloaded the == operator in fruit so that it compares the names of fruit and veg and if they have the same name it tells you. My issue is when I try to compare the whole lists to find duplication, I just cant get the code to work at all.
here is some of my code
plant class
public string name;
public string weight;
public Plant(string name, string weight)
{
this.name = name;
this.email = weight;
}
....
public static bool operator ==(Plant a, Plant b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.name == b.name;
}
then the new fruit constructor
string seeds;
public fruit(string name, string weight, string seeds)
: base(name, weight)
{
this.seeds
}
here is veg
string savoryLevel;
public fruit(string name, string weight, string savouryLevel)
: base(name, weight)
{
this.savoryLevel
}
here is the main where I compare 2 instances, this works fine
Fruit f = new Fruit("apple", "2", "5");
Veg v = new Veg("carrot", "3", "7");
if (f == v)
{
Console.WriteLine("They are the same");
}
else{
Console.WriteLine("They are different");
}
This is the tricky part, I need to iterate through my entire list of veg and fruit and see if any of the fruit have the same name as the veg.
using the lists directly wont work
List<Fruit> fr = new List<Fruit>();
List<Veg> ve = new List<Veg>();
if(fr == ve){
Console.....
}
else{
Console....
}
So how do I get the lists to compare and print out some result to say these are the same or these are not the same?
Any help is really appreciated, thanks.
please just ask if you would like more info.
I think you should use IEquatable<Plant> and cast the lists into List<Plant> with SequenceEquals()
Demo:
public class Plant : IEquatable<Plant>
{
public string Name { get; set; }
public string Weight { get; set; }
public override bool Equals(object obj)
{
var other=obj as Plant;
if(other!=null)
{
return Equals(other);
}
return false;
}
public bool Equals(Plant other)
{
Debug.WriteLine("Checking Equality Between {0} And {1}", Name, other.Name);
return Name.Equals(other.Name);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
public class Fruit : Plant
{
public string Seeds { get; set; }
}
public class Veg : Plant
{
public string SavoryLevel { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Fruit> fruits=new List<Fruit>() {
new Fruit() { Name="apple", Weight = "2", Seeds="5" },
new Fruit() { Name="banana", Weight="1", Seeds="30" }
};
List<Veg> veggies=new List<Veg>() {
new Veg() { Name = "carrot", Weight="3", SavoryLevel="7" },
new Veg() { Name = "potato", Weight="5", SavoryLevel="1" }
};
var equal=fruits.Cast<Plant>().SequenceEqual(veggies);
var unique_fruits=fruits.Distinct();
}
}
It produces the output
Checking Equality Between apple And carrot
and then the equality comparison ends (since it is false). The point being is that it calls the appropriate Equals() function.
If you wanna compare the items at the same index Zip method can be useful:
bool result = fr.Zip(ve, (f,v) => new { f, v }).All(x => x.f == x.v);
Zip methods create pairs of corresponding items, then put each pair into an anonymous type. And All method simply checks if all items in the pairs are equal.
if you want to to it per item, you could do it like this
foreach(var fruit in fr)
{
if(ve.Any(x => x.Name == fruit.Name))
{
Console.Write(fruit.Name + " is in both lists");
}
}
I'd use LINQ, and rather that (or in addition to) overloading the == operator, go for the "more native" object.Equals and object.GetHashCode.
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public override bool Equals(object b)
{
Plant bPlant = b as Plant;
// If one is null, but not both, return false.
if (bPlant == null)
{
return false;
}
// Return true if the fields match:
return this.name == b.name;
}
Then you can use LINQ:
return fr.SequenceEquals(ve);
Note, of course, that, as the name implies, this only works when fr and ve are exactly equal. That is to say, the order must be the same between them: if both contain "Carrot, Broccoli," you'll be fine, but if one is that and the other is "Broccoli, Carrot," this will return false.
Unless I'm misunderstanding, and in fact you want the intersection, not to know that they're equal, in which case:
return fr.Intersect(ve);
You really don't need to overload the Equals method to find out if something is different. Overloading the Equals method should be used if you are looking for a different behaviour, not for different results.
Since this case compares two string members in two different classes, why not just use LINQ to compare the members themselves which are of the same datatype?
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
public static void Main()
{
List<Fruit> f = new List<Fruit>();
Fruit fTemp = new Fruit() { name = "Kiwi" };
f.Add(fTemp);
fTemp = new Fruit() { name = "Tomato" };
f.Add(fTemp);
List<Veg> v = new List<Veg>();
Veg vTemp = new Veg() { name = "Tomato" };
v.Add(vTemp);
List<Veg> vDuplicates = v.Where(vegToCompare=>f.Any(fruitToCompare=>fruitToCompare.name.Equals(vegToCompare.name))).ToList();
vDuplicates.ForEach(a=>Console.WriteLine(a.name));
Console.WriteLine("Number of Duplicates Found: " + vDuplicates.Count);
}
}
public class Fruit
{
public string name;
}
public class Veg
{
public string name;
}
First off thanks everyone for the input, You've all helped me see the problem more clearly.
But Overloading the == operator is something I had to do as part of the requirements.
I have however found a relatively simple way to compare the 2 lists that uses the overloaded == operator I added to the Plant Class
By nesting a forEach loop I check every list item of veg against every list item of fruit.
public void Compare(List<Fruit> frList, List<Veg> vList)
{
foreach (Fruit f in frList)
{
foreach (Veg v in vList)
{
if (f == v)
{
//some functionality
}else{
//some other funtionality
}
}
}
}
This still uses the overloaded == operator in plant and will only compare the name when I call the the method in the main. i.e. even if the plants have different weights they will be considered the same.
Thanks again for the input guys.
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);