【问题标题】:Can't get distinct items from a list using LINQ [duplicate]无法使用 LINQ 从列表中获取不同的项目 [重复]
【发布时间】:2017-06-22 17:26:01
【问题描述】:

我在 C# 中有以下课程,我正在尝试查找不同的项目列表。 该列表有 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 个项目。

唯一的区别是第二个表达式是匿名的,而第一个是输入的。

我有兴趣了解这种行为。

谢谢!

我相信我的问题与上述问题不重复,因为: 我在这里要问的不是如何获得不同的列表。我在问为什么 Typed 和 Anonymous 数据返回不同的结果。

【问题讨论】:

    标签: c# linq anonymous-class


    【解决方案1】:

    Linq 的 Distinct() 方法需要覆盖 GetHashCodeEquals

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

    【讨论】:

    • 所以,你说当使用匿名时,编译器会比较属性,但是当使用类型化数据时,它会查看 GetHashCode 和 Equals 方法?
    • @FLICKER - 编译器不知道你的类中究竟是什么构成了相等。一种方法,我以前做过的方法,是实现IEqualityComparer 并使用 Distinct() 的重载。我最近在这里回答了一个类似的问题-stackoverflow.com/questions/44663909/… 代码在 VB.NET 但概念应该是相同的。
    • @Dai,这是新的,我从未尝试在匿名类型上使用 distinct,所以不知道它们的行为方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多