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;
}
}
这基于“LINQ in Action”中的示例。代码清单4.16。
这两次打印Jon Skeet。为什么?我甚至尝试在Author类中重写Equals方法。仍然不同似乎没有用。我错过了什么?
编辑: 我也添加了==和!=运算符重载。仍然没有帮助。
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
答案 0 :(得分:138)
LINQ Distinct在自定义对象方面并不聪明。
所有这一切都是查看你的列表并看到它有两个不同的对象(它们并不关心它们对于成员字段具有相同的值)。
一种解决方法是实现IEquatable接口,如图here所示。
如果您修改了您的作者类,它应该可以工作。
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;
}
}
答案 1 :(得分:62)
Distinct()
方法检查引用类型的引用相等性。这意味着它在字面上寻找相同的对象,而不是包含相同值的不同对象。
有一个overload需要IEqualityComparer,因此您可以指定不同的逻辑来确定给定的对象是否等于另一个。
如果希望Author通常表现得像普通对象(即只是引用相等),但为了通过名称值检查相等性,请使用 IEqualityComparer 。如果您始终希望根据名称值比较Author对象,则覆盖GetHashCode和Equals ,或实现IEquatable 。
IEqualityComparer
界面上的两位成员是Equals
和GetHashCode
。您确定两个Author
对象是否相等的逻辑似乎是First和Last名称字符串相同。
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();
}
}
答案 2 :(得分:39)
没有实施IEquatable
,Equals
和GetHashCode
的另一个解决方案是使用LINQ {1}}方法并从IGrouping中选择第一项。
GroupBy
答案 3 :(得分:20)
Distinct()
对枚举中的对象执行默认的相等比较。如果您没有覆盖Equals()
和GetHashCode()
,那么它会使用object
上的默认实现,它会比较引用。
简单的解决方案是将Equals()
和GetHashCode()
的正确实现添加到参与您正在比较的对象图(即Book和Author)的所有类中。
IEqualityComparer
界面是一种便利,当您无法访问需要比较的类的内部时,可以在单独的类中实现Equals()
和GetHashCode()
,或者如果您使用不同的比较方法。
答案 4 :(得分:19)
还有一种方法可以从用户定义的数据类型列表中获取不同的值:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
当然,它会提供不同的数据集
答案 5 :(得分:10)
你已经覆盖了Equals(),但要确保你也覆盖了GetHashCode()
答案 6 :(得分:7)
以上答案错了! MSDN上声明的区别是返回默认的Equator,其声明为 Default属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。 否则,它返回一个EqualityComparer,它使用由T 提供的Object.Equals和Object.GetHashCode的覆盖
这意味着只要你超越Equals就可以了。
您的代码无效的原因是您检查firstname == lastname。
请参阅https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx和https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx
答案 7 :(得分:0)
您可以在列表中使用扩展方法,该方法根据计算得出的哈希值检查唯一性。 您还可以更改扩展方法以支持IEnumerable。
示例:
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.
扩展方法:
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("-", "");
}
答案 8 :(得分:0)
以下代码中的 Equal 运算符不正确。
旧
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
新
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;
}
答案 9 :(得分:-1)
您可以通过两种方式实现这一目标:
1。。您可以实现Enumerable.Distinct Method所示的IEquatable接口,也可以看到@skalb's answer at this post
2。。如果您的对象没有唯一键,则可以使用GroupBy方法来获得不同的对象列表,必须对对象的所有属性进行分组,然后再选择第一个对象。
例如如下所示,并为我工作:
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类如下:
public class MyClass{
public string Name{get;set;}
public string Phone{get;set;}
public string Email{get;set;}
public string Country{get;set;}
}
3。。如果您的对象具有唯一键,则只能在分组依据下使用它。
例如,我对象的唯一键是ID。
var distinctList= list.GroupBy(x =>x.Id)
.Select(x => x.First())
.ToList()