Не удается получить отдельные элементы из списка с помощью LINQ

У меня есть следующий класс на С#, и я пытаюсь найти отдельный список элементов. В списке 24 элемента.

public enum DbObjectType
{
    Unknown,
    Procedure,
    Function,
    View
}

public class DbObject
{
    public string DatabaseName { get; set; }
    public string SchemaName { get; set; }
    public string ObjectName { get; set; }
    public DbObjectType ObjectType { get; set; }
}

У меня есть подход к буксировке, и я ожидаю получить тот же результат, но я этого не делаю.

первое выражение возвращает мне тот же список (включая дубликаты)

var lst1 = from c in DependantObject
          group c by new DbObject
          {
              DatabaseName = c.DatabaseName,
              SchemaName = c.SchemaName,
              ObjectName = c.ObjectName,
              ObjectType = c.ObjectType
          } into grp
          select grp.First();

lst1 будет иметь 24 элемента.

но этот возвращает желаемый результат.

var lst2 = from c in DependantObject
          group c by new 
          {
              DatabaseName = c.DatabaseName,
              SchemaName = c.SchemaName,
              ObjectName = c.ObjectName,
              ObjectType = c.ObjectType
          } into grp
          select grp.First();

lst2 будет иметь 10 элементов.

Единственная разница в том, что второе выражение анонимно, а первое типизировано.

Мне интересно понять это поведение.

Благодарю вас!

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


person FLICKER    schedule 22.06.2017    source источник


Ответы (1)


Метод Linq Distinct() требует переопределения GetHashCode и Equals.

Анонимные типы C# (синтаксис new { Name = value }) создают классы, которые переопределяют эти методы, а ваш собственный тип DbObject — нет.

Вы также можете создать собственный тип IEqualityComparer. Посмотрите еще на StructuralComparisons.StructuralEqualityComparer.

Опция 1:

public class DbObject : IEquatable<DbObject> {

    public override Int32 GetHashCode() {

        // See https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode

        unchecked
        {
            int hash = 17;
            hash = hash * 23 + this.DatabaseName.GetHashCode();
            hash = hash * 23 + this.SchemaName.GetHashCode();
            hash = hash * 23 + this.ObjectName.GetHashCode();
            hash = hash * 23 + this.ObjectType.GetHashCode();
            return hash;
        }
    }

    public override Boolean Equals(Object other) {

        return this.Equals( other as DbObject );    
    }

    public Boolean Equals(DbObject other) {

        if( other == null ) return false;
        return
            this.DatabaseName.Equals( other.DatabaseName ) &&
            this.SchemaName.Equals( other.SchemaName) &&
            this.ObjectName.Equals( other.ObjectName ) &&
            this.ObjectType.Equals( other.ObjectType);
    }
}

Вариант 2:

class DbObjectComparer : IEqualityComparer {

    public Boolean Equals(DbObject x, DbObject y) {

        if( Object.ReferenceEquals( x, y ) ) return true;
        if( (x == null) != (y == null) ) return false;
        if( x == null && y == null ) return true;

         return
            x.DatabaseName.Equals( y.DatabaseName ) &&
            x.SchemaName.Equals( y.SchemaName) &&
            x.ObjectName.Equals( y.ObjectName ) &&
            x.ObjectType.Equals( y.ObjectType);
    }

    public override Int32 GetHashCode(DbObject obj) {

        unchecked
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + obj.DatabaseName.GetHashCode();
            hash = hash * 23 + obj.SchemaName.GetHashCode();
            hash = hash * 23 + obj.ObjectName.GetHashCode();
            hash = hash * 23 + obj.ObjectType.GetHashCode();
            return hash;
        }
    }
}

Вариант 2 использования:

var query = this.DependantObject
    .GroupBy( c => new DbObject() {
        DatabaseName = c.DatabaseName,
        SchemaName   = c.SchemaName,
        ObjectName   = c.ObjectName,
        ObjectType   = c.ObjectType
    } )
    .First();

Использование GroupBy может быть неоптимальным, вы можете напрямую использовать Linq Distinct:

var query = this.DependantObject
    .Select( c => new DbObject() {
        DatabaseName = c.DatabaseName,
        SchemaName   = c.SchemaName,
        ObjectName   = c.ObjectName,
        ObjectType   = c.ObjectType
    } )
    .Distinct()
    .First();
person Dai    schedule 22.06.2017
comment
Итак, вы говорите, что при использовании анонимного компилятор сравнивает свойства, но при использовании типизированных данных он будет изучать методы GetHashCode и Equals? - person FLICKER; 22.06.2017
comment
@FLICKER - компилятор не будет знать, что именно представляет собой равенство в вашем классе. Один из способов сделать это, как я делал это раньше, — реализовать IEqualityComparer и использовать перегрузку Distinct(). Недавно я ответил на аналогичную проблему здесь - stackoverflow.com/questions/44663909/ Код находится в VB.NET, но концепция должна быть той же. - person Fabulous; 22.06.2017
comment
@Дай, это новинка, я никогда не пытался использовать отдельные анонимные типы, поэтому не знал, как они себя ведут. - person Fabulous; 22.06.2017