2013-08-05 5 views
2

Я решил проблему, с которой я столкнулся, но хотя я узнал, как что-то работает (или нет), я не понимаю, почему.Linq/Enumerable Any Vs Содержит

Как я тип человека, который любит, чтобы знать «почему» я надеюсь, что кто-то может объяснить:

У меня есть список элементов и связанных с ними комментарии, и я хотел провести различие между комментариями и администратора комментарии пользователей, поэтому я попытался следующий код:

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 
} 

Stepping через код показал, что, хотя он был правильный объект Role, он не признает роль в commentUser.Roles

код, в конечном итоге работал:

if(commentUser.Roles.Any(x=>x.Name == "admin")) 
{ 
    //do stuff 
} 

Я доволен этим, потому что это меньше кода и, по-моему, более чистым, но я не понимаю, как он не работает.

Надеясь, что кто-то может очистить это для меня.

ответ

8

Это вероятно, потому что вы не отменяют сравнения равенства (Equals, GetHashCode, operator==) на вашем Role классе. Поэтому он делал сравнение ссылок, которое на самом деле не самая лучшая идея, как если бы они не были одним и тем же объектом, это заставляет думать, что это другое. Для обеспечения равенства ценности вам необходимо переопределить эти операторы равенства.

+0

Я не 't получить его - это не User.Roles список объектов Role? – richardterris

+2

Поскольку он не использовал '==' между объектом Role, неважно, перегрузил ли он 'operator ==', но вы правы в переопределении 'Equals' и' GetHashCode'. –

+2

@richardterris Все дело в том, как ваш объектный контекст имеет дело со вторым запросом для того же объекта. Похоже, что поведение состоит в том, чтобы создать новый объект, а не возвращать уже запрошенный объект. Это означает, что, несмотря на то, что все значения свойств выходят одинаково, они фактически являются разными объектами и без переопределения равенства значение возвращает false. –

1

Это будет ваше сравнение равенства для роли.

Объект в commentUserRole - это не тот объект, который вы ищете commentUser.Roles.

Ваш объект контекста создаст новый объект, когда вы выберете его, и заполните его свойство Роли коллекцией новых ролей. Если ваш контекст не отслеживает объекты, чтобы вернуть один и тот же объект при запросе второй копии, то это будет другой объект, хотя все свойства могут быть одинаковыми. Таким образом, выход из строя Содержит

Ваш пункт Any явно проверять свойство Имя, которое почему он работает

Попробуйте сделать Роль реализации IEquatable<Role>

public class Role : IEquatable<Role> { 
    public bool Equals(Role compare) { 
    return compare != null && this.Name == compare.Name; 
    } 
} 

Хотя MSDN показывает вам только это нужно для List<T> вы может потребоваться переопределить Equals и GetHashCode, чтобы сделать эту работу

и в этом случае:

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

Спасибо за помощь. Я прокомментировал комментарий «It'sNotALie», но я думаю, что вы оба говорите почти то же самое. – richardterris

1

При использовании Contains -метода, вы проверяете, если массив Roles из пользовательского объекта содержит объект, вы извлеченные из базы данных заранее. Хотя массив содержит объект для роли «admin», он не содержит точного объекта, который вы выбрали ранее.

При использовании метода Any вы проверяете, есть ли какая-либо роль с именем «admin» - и который обеспечивает ожидаемый результат.

Чтобы получить тот же результат с помощью метода Contains -method, введите значение класса ролей и сравните его, чтобы проверить, действительно ли два экземпляра имеют одинаковое значение.

4

Вам необходимо переопределить Equals (и всегда также GetHashCode), если вы хотите использовать Contains. В противном случае 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<Role> для перегрузки Enumerable.Contains:

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)) 
{ 
    // ... 
} 
+0

Конечно, если «Роль» - это класс, который не «запечатан», некоторые люди предпочитают использовать 'return GetType() == r2.GetType() && RoleID == r2.RoleID;'. В противном случае будет сложно (даже для осторожных кодировщиков) получить «Роль» и получить правильное значение «Их». –

+0

@JeppeStigNielsen: Я уже добавил другой вариант, он может использовать 'IEqualityComparer '. В этом случае ему не нужно было изменять «Роль». –

+0

Мне это нравится. Мой комментарий применим только к вашей первой «опции», изменяя сам тип «Роль», и, поскольку вы не удалили эту опцию из своего ответа, мне все еще хочется комментировать это. –

Смежные вопросы