我尝试减去2个列表,如下面的代码,assignUsers
有3条记录,assignedUsers
有2行。在Except
方法之后,我仍然得到3行,但我应该获得1条记录,因为assignedUsers
中的2行与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();
答案 0 :(得分:27)
为了使Except
方法按预期工作,类AssignUserViewModel
必须正确覆盖GetHashCode
和Equals
方法。
例如,如果AssignUserViewModel
对象由Id
唯一定义,则应以这种方式定义类:
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);
}
}
否则,如果您不能/不想更改类实现,则可以实现IEqualityComparer<>
并将其传递给Except
方法,例如:
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();
}
}
然后你的最后一行将成为:
assignUsers = assignUsers.Except(assignedUsers, new AssignUserViewModelEqualityComparer()).ToList();
答案 1 :(得分:2)
为什么会这样?
当您使用Set Operations(Distinct,Except,Intersect,Union)时,Linq需要比较序列元素是否相等。默认情况下,Linq使用Object.Equals和Object.GetHashCode方法来比较元素。如果未在类型中覆盖这些方法,则使用基类的实现,该实现通过引用相等性比较对象。默认实现保证两个相同引用的对象具有相同的哈希码(因此被认为是相同的)。这是你的情况。 Mapper
类创建AssignUserViewModel
个对象的新实例,这些实例具有不同的引用,并且不能被视为相等(即使所有字段值都相同)。
那么,我们能做些什么?
覆盖班级中的Equals
和GetHashCode
方法。由您决定如何对待对象 - 所有领域或仅仅是身份。 Linq将使用您的方法来比较元素。
提供您自己的比较器(通常情况下,您无法修改对象并覆盖Equals
和GetHashCode
。是的,所有Linq Set Operations都有两个重载 - 一个使用默认比较器和其他,接受你的IEqualityComparer<T>
。
使用匿名类型。所有匿名类型都已生成方法Equals
和GetHashCode
,它们使用所有属性的比较来确定对象是否相等。在这种情况下,您既不需要修改类型也不需要创建比较器。
因此,您已经有前两种方法的样本,这是最后一种:
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();