C# Linq/可枚举任何Vs包含

C# Linq/可枚举任何Vs包含,c#,linq,enumerable,C#,Linq,Enumerable,我已经解决了我遇到的一个问题,但尽管我已经发现了某些东西是如何工作的(或不工作的),但我不清楚为什么 因为我是那种喜欢知道“为什么”的人,我希望有人能解释: 我有项目列表和相关评论,我想区分管理员评论和用户评论,所以我尝试了以下代码: User commentUser = userRepository.GetUserById(comment.userId); Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");

我已经解决了我遇到的一个问题,但尽管我已经发现了某些东西是如何工作的(或不工作的),但我不清楚为什么

因为我是那种喜欢知道“为什么”的人,我希望有人能解释:

我有项目列表和相关评论,我想区分管理员评论和用户评论,所以我尝试了以下代码:

User commentUser = userRepository.GetUserById(comment.userId);
Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");
if(commentUser.Roles.Contains(commentUserRole)
 {
   //do stuff
 }
else
{
 // do other stuff
}
逐步检查代码表明,尽管它具有正确的角色对象,但它无法识别commentUser.Roles中的角色

最终起作用的代码是:

if(commentUser.Roles.Any(x=>x.Name == "admin"))
{
  //do stuff
}
我很高兴这样做,因为它的代码更少,而且在我看来更干净,但我不明白contains是如何不起作用的


希望有人能帮我澄清。

这可能是因为您没有覆盖
角色
类上的相等比较(
等于
GetHashCode
操作符==
)。因此,它在做参考比较,这真的不是最好的主意,好像它们不是同一个对象,它使它认为这是不同的。您需要重写这些相等运算符以提供值相等。

这将是角色的相等比较

commentUserRole
中的对象与您要查找的对象不同

当您从上下文对象中选择并用新角色集合填充角色属性时,上下文对象将创建一个新对象。如果您的上下文没有跟踪对象,以便在请求第二个副本时返回相同的对象,那么它将是不同的对象,即使所有属性可能相同。因此,控制的失败

您的
Any
子句正在显式检查Name属性,这就是它工作的原因

尝试使角色实现
IEquatable


使用
Contains
-方法时,检查用户对象的数组
Roles
是否包含您事先从数据库检索到的对象。虽然数组包含角色“admin”的对象,但它不包含您以前获取的确切对象

当使用
Any
-方法时,您将检查是否有任何名为“admin”的角色,并且该角色可以提供预期的结果


要使用
Contains
-方法获得相同的结果,请在role类上实现
IEquatable
-接口,并比较名称以检查两个实例是否实际具有相同的值。

您必须重写
Equals
(并且始终
GetHashCode
)如果要使用
包含
。否则,
Equals
只会比较引用

例如:

public class Role
{
    public string RoleName{ get; set; }
    public int RoleID{ get; set; }
    // ...

    public override bool Equals(object obj)
    {
        Role r2 = obj as Role;
        if (r2 == null) return false;
        return RoleID == r2.RoleID;
    }
    public override int GetHashCode()
    {
        return RoleID;
    }
    public override string ToString()
    {
        return RoleName;
    } 
}
另一个选项是为以下内容的重载实现自定义
IEqualityComparer


我不明白-User.Roles不是角色对象列表吗?因为他没有在角色对象之间使用
=
,所以如果他重载
操作符==
,这并不重要,但是,覆盖
Equals
GetHashCode
@richardtries是正确的,这取决于对象上下文如何处理对同一对象的第二个请求。看起来行为是创建一个新对象,而不是返回已请求的对象。这意味着即使所有属性值都是相同的,它们实际上是不同的对象,没有相等覆盖,值返回false。@JeppeStigNielsen只是为了一致性:我知道在这个例子中他不需要它。谢谢你的帮助-我已经对'it'sNotALie'的注释进行了评论-但是我想如果
Role
是一个不是
密封的
,有些人更喜欢使用
return GetType()==r2.GetType()&&RoleID==r2.RoleID。否则(即使是细心的程序员)也很难从
角色
中派生出他们的
等价物
。@JeppeStigNielsen:我已经添加了另一个选项,他可以使用
IEqualityComparer
。这样他就不需要修改角色本身了。我喜欢这样。我的评论只与您的第一个“选项”相关,即修改
角色
类型本身,既然您没有从答案中删除该选项,我仍然想对此发表评论。我想问题是,这是否比使用.Any检查名称更好?@richardterris:不一定,这都是关于可读性的:如果您经常需要这个类,并且需要经常相互比较角色(例如,在
Enumerable.Contains
)中,最好重写
Equals
GetHashCode
。如果你能修改这个类,这是最好的做法。如果不能,您仍然可以使用自定义的
IEqualityComparer
,如上所示。然后,您甚至可以在其他linq方法中使用比较器,如
GroupBy
Intersect
Distinct
Join
等。。如果您无法覆盖
Equals
+
GetHashCode
,则所有这些方法都会为
IEqualityComparer
提供重载版本。
public class Role : IEquatable<Role> {
  public bool Equals(Role compare) {
    return compare != null && this.Name == compare.Name;
  }

  public override bool Equals(object compare) {
     return this.Equals(compare as Role); // this will call the above equals method
  }

  public override int GetHashCode() {
     return this.Name == null ? 0 : this.Name.GetHashCode();
  }
}
public class Role
{
    public string RoleName{ get; set; }
    public int RoleID{ get; set; }
    // ...

    public override bool Equals(object obj)
    {
        Role r2 = obj as Role;
        if (r2 == null) return false;
        return RoleID == r2.RoleID;
    }
    public override int GetHashCode()
    {
        return RoleID;
    }
    public override string ToString()
    {
        return RoleName;
    } 
}
public class RoleComparer : IEqualityComparer<Role>
{
    public bool Equals(Role x, Role y)
    {
        return x.RoleID.Equals(y.RoleID);
    }

    public int GetHashCode(Role obj)
    {
        return obj.RoleID;
    }
}
var comparer = new RoleComparer();
User commentUser = userRepository.GetUserById(comment.userId);
Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");
if(commentUser.Roles.Contains(commentUserRole, comparer))
{
    // ...
}