LINQ选择与匿名类型不同


150

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

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

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


这是LINQ到对象还是LINQ到SQL?如果只是对象,那么您可能很不幸。但是,如果使用L2S,则它可能会起作用,因为DISTINCT将传递到SQL语句中。
詹姆斯·柯伦

Answers:


188

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

和所有人平等...匿名类型

简短的答案(我引用):

事实证明,C#编译器针对匿名类型覆盖了Equals和GetHashCode。这两个重写方法的实现使用该类型上的所有公共属性来计算对象的哈希码并测试是否相等。如果相同匿名类型的两个对象的属性都具有相同的值,则这些对象相等。

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


2
我认为这仅是正确的,如果属性本身是值类型或实现值相等-请参阅我的答案。
tvanfosson

是的,因为它在每个属性上都使用GetHashCode,所以只有每个属性都有自己的唯一实现时,它才有效。我认为大多数用例只将简单类型用作属性,因此通常是安全的。
Matt Hamilton 2009年

4
结束语意味着两种匿名类型的相等性取决于成员的相等性,这对我来说很好,因为成员的定义是在某个我可以到达的地方定义的,并且在必须时可以覆盖相等性。我只是不想为此创建一个类来覆盖equals。
GWLlosa

3
可能值得MS向VB引入C#中的“键”语法(您可以在其中将匿名类型的某些属性指定为“主键”,请参阅我链接到的博客文章)。
Matt Hamilton 2009年

1
非常有趣的文章。谢谢!
Alexander Prokofyev

14
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。如果两者objectstring仍然返回重复的行。尝试FirstNameis is typeof object并在string那里指定相同的值。
CallMeLaNN

对于键入的对象,这是一个很好的答案,但对于匿名类型,则不需要。
crokusek

5

有趣的是,它可以在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()

11
如果您将Key关键字添加到匿名类型,.Distinct()它将按预期工作(例如New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()})。
Cᴏʀʏ

3
科里是对的。C#代码的正确翻译new {A = b}New {Key .A = b}。匿名VB类中的非关键属性是可变的,这就是为什么通过引用比较它们的原因。在C#中,匿名类的所有属性都是不可变的。
Heinzi 2011年

4

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


2

您可以创建自己的采用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();
    }       
}

然后创建您独特的扩展方法

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仍然返回重复的行。尝试FirstNameis is typeof object并在string那里指定相同的值。
CallMeLaNN

0

如果AlphaBravo两者都继承自一个公共类,则可以通过实现来指示父类中的相等性检查IEquatable<T>

例如:

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 <T>的匿名类型类的属性,则会调用Equals而不是默认行为(通过反射检查所有公共属性?)
D_Guidi 2011年

0

嘿,我遇到了同样的问题,我找到了解决方案。您必须实现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个属性(Name,Age,IsEgyptian“因为我是”)。在GetHashCode中,我返回了Name属性的哈希,而不是Person对象。

尝试一下,它将可以运行ISA。谢谢Modather Sadik


1
GetHashCode应该使用比较中使用的所有相同字段和属性来实现相等性,而不仅仅是其中之一。即public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG在SD

有关生成一个好的哈希算法的信息:stackoverflow.com/questions/263400/...
在SD JG

0

为了使其能够在VB.NET中工作,您需要Key在匿名类型的每个属性之前指定关键字,如下所示:

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

我为此感到挣扎,我以为VB.NET不支持这种功能,但实际上支持。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.