【问题标题】:LINQ Select Distinct with Anonymous TypesLINQ Select Distinct with Anonymous Types
【发布时间】:2010-10-07 07:22:46
【问题描述】:

所以我有一个对象集合。确切的类型并不重要。从中我想提取一对特定属性的所有唯一对,因此:

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

所以我的问题是:在这种情况下 Distinct 会使用默认对象 equals (这对我来说没用,因为每个对象都是新的)还是可以告诉它做不同的 equals (在这种情况下,相等的值Alpha 和 Bravo => 相等的实例)?如果不这样做,有什么方法可以达到这个结果?

【问题讨论】:

  • 这是 LINQ-to-Objects 还是 LINQ-to-SQL?如果只是对象,您可能不走运。但是,如果是 L2S,那么它可能会起作用,因为 DISTINCT 将被传递到 SQL 语句中。

标签: c# linq distinct anonymous-types equality


【解决方案1】:

在这里阅读 K. Scott Allen 的精彩文章:

And Equality for All ... Anonymous Types

简短的回答(我引用):

原来是 C# 编译器覆盖 匿名的 Equals 和 GetHashCode 类型。两者的实施 被覆盖的方法使用所有公共的 计算类型的属性 对象的哈希码和测试 平等。如果两个相同的对象 匿名类型都一样 它们的属性值—— 对象是相等的。

因此,在返回匿名类型的查询上使用 Distinct() 方法是完全安全的。

【讨论】:

  • 我认为,如果属性本身是值类型或实现值相等,这才是正确的——请参阅我的答案。
  • 是的,因为它在每个属性上都使用 GetHashCode,所以它只有在每个属性都有自己独特的实现时才能工作。我认为大多数用例只涉及简单类型作为属性,因此通常是安全的。
  • 这意味着两个匿名类型的相等性取决于成员的相等性,这对我来说很好,因为成员是在某个地方定义的,我可以到达并覆盖相等性,如果我不得不。我只是不想为此创建一个类来覆盖equals。
  • 可能值得请愿 MS 将“键”语法引入 VB 所具有的 C#(您可以在其中指定匿名类型的某些属性作为“主键” - 请参阅博客文章 I链接到)。
  • @tvanfosson 我对分配给匿名类型属性的类对象进行了快速测试,正如我所料,它似乎回退到这些属性的对象引用相等性。
【解决方案2】:
public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

抱歉之前的格式混乱

【讨论】:

  • 此扩展无法处理objectobject 类型。如果 object 都是 string 它仍然返回重复的行。尝试 FirstName 是 typeof object 并在那里分配相同的 string
  • 这对于类型化对象来说是一个很好的答案,但对于匿名类型却不需要。
【解决方案3】:

有趣的是它可以在 C# 中工作,但不能在 VB 中工作

返回 26 个字母:

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

返回 52...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()

【讨论】:

  • 如果您将 Key 关键字添加到匿名类型,.Distinct() 将按预期工作(例如 New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()})。
  • 科里是对的。 C# 代码new {A = b} 的正确翻译是New {Key .A = b}。匿名 VB 类中的非键属性是可变的,这就是它们通过引用进行比较的原因。在 C# 中,匿名类的所有属性都是不可变的。
【解决方案4】:

我做了一个小测试,发现如果属性是值类型,它似乎可以正常工作。如果它们不是值类型,则该类型需要提供它自己的 Equals 和 GetHashCode 实现才能使其工作。我认为字符串会起作用。

【讨论】:

    【解决方案5】:

    您可以创建自己的采用 lambda 表达式的 Distinct Extension 方法。这是一个例子

    创建一个派生自 IEqualityComparer 接口的类

    public class DelegateComparer<T> : IEqualityComparer<T>
    {
        private Func<T, T, bool> _equals;
        private Func<T, int> _hashCode;
        public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
        {
            _equals= equals;
            _hashCode = hashCode;
        }
        public bool Equals(T x, T y)
        {
            return _equals(x, y);
        }
    
        public int GetHashCode(T obj)
        {
            if(_hashCode!=null)
                return _hashCode(obj);
            return obj.GetHashCode();
        }       
    }
    

    然后创建你的 Distinct Extension 方法

    public static class Extensions
    {
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
            Func<T, T, bool> equals, Func<T,int> hashCode)
        {
            return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
        }
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
            Func<T, T, bool> equals)
        {
            return items.Distinct(new DelegateComparer<T>(equals,null));
        }
    }
    

    你可以使用这个方法找到不同的项目

    var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
                .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();
    

    【讨论】:

    • 此扩展无法处理objectobject 类型。如果两个 objectstring 它仍然返回重复的行。尝试 FirstName 是 typeof object 并在那里分配相同的 string
    【解决方案6】:

    如果AlphaBravo 都继承自一个公共类,您将能够通过实现IEquatable&lt;T&gt; 来指示父类中的相等检查。

    例如:

    public class CommonClass : IEquatable<CommonClass>
    {
        // needed for Distinct()
        public override int GetHashCode() 
        {
            return base.GetHashCode();
        }
    
        public bool Equals(CommonClass other)
        {
            if (other == null) return false;
            return [equality test];
        }
    }
    

    【讨论】:

    • 因此,如果您将实现 IEquatable 的匿名类型类用作属性,则会调用 Equals 而不是默认行为(通过反射检查所有公共属性?)
    【解决方案7】:

    嘿,我遇到了同样的问题,我找到了解决方案。 您必须实现 IEquatable 接口或简单地覆盖 (Equals & GetHashCode) 方法。但这不是技巧,GetHashCode 方法中的技巧。你不应该返回你的类对象的哈希码,但你应该返回你想要比较的属性的哈希。

    public override bool Equals(object obj)
        {
            Person p = obj as Person;
            if ( obj == null )
                return false;
            if ( object.ReferenceEquals( p , this ) )
                return true;
            if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
                return true;
            return false;
            //return base.Equals( obj );
        }
        public override int GetHashCode()
        {
            return Name.GetHashCode();
        }
    

    如您所见,我有一个名为 person 的类,它有 3 个属性(姓名、年龄、IsEgyptian“因为我是”)在 GetHashCode 中,我返回了 Name 属性的哈希值,而不是 Person 对象。

    试一试,它将适用于 ISA。 谢谢, 莫达瑟·萨迪克

    【讨论】:

    • GetHashCode 应该使用在相等性比较中使用的所有相同字段和属性,而不仅仅是其中一个。即public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
    • 有关生成良好哈希算法的信息:stackoverflow.com/questions/263400/…
    【解决方案8】:

    为了让它在 VB.NET 中工作,你需要在匿名类型的每个属性之前指定 Key 关键字,就像这样:

    myObjectCollection.Select(Function(item) New With
    {
        Key .Alpha = item.propOne,
        Key .Bravo = item.propTwo
    }).Distinct()
    

    我为此苦苦挣扎,我认为 VB.NET 不支持这种类型的功能,但实际上它支持。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-03
      • 2017-11-25
      • 1970-01-01
      • 2012-11-15
      • 2020-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多