Distinct not working with LINQ to Objects

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;
}
124336 次浏览

Distinct()方法检查引用类型的引用相等性。这意味着它正在寻找字面上相同的对象复制,而不是不同的对象,其中包含相同的值。

有一个 超载接受一个 平等比较器,所以您可以指定不同的逻辑来确定一个给定的对象是否等于另一个。

如果希望 Author 的行为通常像一个普通对象(即仅引用相等性) ,但是为了通过名称值来区分检查相等性,请使用 平等比较器。如果您总是希望根据名称值比较 Author 对象,则选择 覆盖 GetHashCode 和 Equals实现 IEquable

IEqualityComparer接口上的两个成员是 EqualsGetHashCode。用于确定两个 Author对象是否相等的逻辑似乎是“名字”和“姓氏”字符串是否相同。

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();
}
}

当涉及到自定义对象时,LINQdistent 并不是那么聪明。

它所做的只是查看您的列表,看到它有两个不同的对象(它不关心它们对于成员字段具有相同的值)。

一种解决方案是实现如 给你所示的 IEqutable 接口。

如果您像这样修改 Author 类,那么它应该可以工作。

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;
}
}

试试用 DotNetFiddle

您已经重写了 Equals () ,但是请确保您也重写了 GetHashCode ()

Distinct()对枚举数中的对象执行默认的相等性比较。如果没有重写 Equals()GetHashCode(),那么它将使用 object上的默认实现,该实现比较引用。

简单的解决方案是将 Equals()GetHashCode()正确实现添加到所有参与对象图的类中(即 Book 和 Author)。

IEqualityComparer接口非常方便,当您不能访问需要比较的类的内部,或者使用不同的比较方法时,可以在单独的类中实现 Equals()GetHashCode()

另一个没有实现 IEquatableEqualsGetHashCode的解决方案是使用 LINQs GroupBy方法并从 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);
}

以上答案是错误的! ! ! 如 MSDN 中所述,显著返回缺省 Equator,如 < em > Default 属性检查 T 类型是否实现 System.IEqutable 接口,如果是,返回一个使用该实现的 EquityComparer。否则,它返回一个使用 Object 重写的 EqualityComparer。平等与对象。T 提供的 GetHashCode

也就是说只要你重写了 Equals 就没问题。

代码无法工作的原因是您检查了 firstname = = lastname。

https://msdn.microsoft.com/library/bb348436(v=vs.100).aspxhttps://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx

还有一种从用户定义的数据类型列表中获取不同值的方法:

YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();

毫无疑问,它将提供一组独特的数据

可以对列表使用扩展方法,该方法根据计算出的 Hash 检查唯一性。 还可以更改扩展方法以支持 IEnumable。

例如:

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("-", "");
}

你可以通过以下几种方式实现这一点:

1.你可以实现如 可枚举的,独特的方法所示的 IEqutable 接口,或者你可以在这篇文章中看到这个问题的答案

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()

下面代码中的“相等”运算符不正确。

老了

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;
}