익명 유형을 가진 LINQ Select Distinct


150

그래서 객체 모음이 있습니다. 정확한 유형은 중요하지 않습니다. 그것으로부터 특정 속성 쌍의 모든 고유 쌍을 추출하고 싶습니다.

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

그래서 내 질문은 :이 경우 기본 객체 같음 (각 객체가 새 것이기 때문에 나에게 쓸모가 없음)을 사용하거나 다른 같음 (이 경우 알파와 브라보의 동일한 값)을 수행하도록 지시받을 수 있습니다 => 동일한 인스턴스)? 그렇지 않은 경우 해당 결과를 달성 할 수있는 방법이 있습니까?


이 LINQ-to-Objects입니까, LINQ-to-SQL입니까? 그냥 이의를 제기한다면 아마 운이 나쁠 것입니다. 그러나 L2S 인 경우 DISTINCT가 SQL 문에 전달되므로 작동 할 수 있습니다.
James Curran

답변:


188

K. Scott Allen의 훌륭한 게시물을 읽으십시오.

그리고 모두를위한 평등 ... 익명 유형

짧은 대답 (및 인용) :

익명 유형에 대해 C # 컴파일러가 Equals 및 GetHashCode를 대체합니다. 재정의 된 두 메서드의 구현은 형식의 모든 공용 속성을 사용하여 개체의 해시 코드를 계산하고 동등성을 테스트합니다. 익명 유형이 동일한 두 개체의 속성 값이 모두 같은 경우 개체가 동일합니다.

따라서 익명 형식을 반환하는 쿼리에서 Distinct () 메서드를 사용하는 것이 안전합니다.


2
이것은 속성 자체가 값 유형이거나 값 평등을 구현하는 경우에만 사실이라고 생각합니다. 내 대답을 참조하십시오.
tvanfosson

예, 각 속성에 대해 GetHashCode를 사용하므로 각 속성에 고유 한 구현이있는 경우에만 작동합니다. 대부분의 유스 케이스에는 단순한 유형이 속성으로 포함되므로 일반적으로 안전하다고 생각합니다.
매트 해밀턴

4
그것은 익명의 두 유형의 평등이 회원의 평등에 달려 있다는 것을 의미합니다. 회원은 내가 가질 수 있고 평등을 무시할 수있는 장소에 정의되어 있기 때문에 괜찮습니다. 나는 그냥 equals를 재정의하기 위해 클래스를 만들고 싶지 않았습니다.
GWLlosa

3
VB가 가지고있는 C #에 "키"구문을 도입하는 것이 MS에 탄원하는 것이 좋습니다 (여기서 익명 유형의 특정 속성을 '기본 키'로 지정할 수 있습니다-링크 된 블로그 게시물 참조).
매트 해밀턴

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

이전에 엉망인 형식으로 죄송합니다.


이 확장은 object및 유형을 처리 할 수 ​​없습니다 object. 둘 object다인 string경우 여전히 중복 행을 리턴합니다. FirstNameis 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

4

약간의 테스트를 실행하여 속성이 값 유형 인 경우 제대로 작동하는 것으로 나타났습니다. 값 유형이 아닌 경우, 유형이 작동하려면 자체 Equals 및 GetHashCode 구현을 제공해야합니다. 문자열은 효과가 있다고 생각합니다.


2

람다 식을 사용하는 고유 한 고유 확장 방법을 만들 수 있습니다. 여기에 예가 있습니다

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

이 확장은 object및 유형을 처리 할 수 ​​없습니다 object. 둘 object다인 string경우 여전히 중복 행을 리턴합니다. FirstNameis 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

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 "Breuse I am") GetHashCode에서 Person 개체가 아닌 Name 속성의 해시를 반환했습니다.

그것을 시도하면 ISA가 작동합니다. 감사합니다, Modather Sadik


1
GetHashCode는 평등을 위해 비교에 사용되는 동일한 필드와 속성을 하나만 사용해야합니다. 즉public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
SD의 JG

좋은 해시 알고리즘 생성에 대한 정보 : 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은 이러한 유형의 기능을 지원하지 않지만 실제로는 지원한다고 생각했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.