여러 참조 유형 필드의 LINQ GroupBy;사용자 정의 평등 비교기
-
13-12-2019 - |
문제
그래서 나는 SO와 다른 곳에서 이에 대한 약 20개의 예제를 살펴봤지만 내가 하려는 작업을 다루는 예제를 찾지 못했습니다.이것 - 명시적 유형 비교기를 인라인으로 지정할 수 있나요? - 필요한 것 같지만 충분하지 않습니다(또는 더 나아가는 방법을 이해하지 못합니다).
- LoadData 목록이 있고 LoadData 개체에는 참조 유형과 값 유형의 필드가 모두 있습니다.
- 참조 필드와 값 필드를 혼합하여 그룹화하고 출력을 익명 유형으로 프로젝션해야 합니다.
GroupBy 필드를 비교하는 방법을 지정하려면 사용자 지정 IEqualityComparer를 제공해야 하지만 익명 유형입니다.
private class LoadData { public PeriodEndDto PeriodEnd { get; set; } public ComponentDto Component { get; set; } public string GroupCode { get; set; } public string PortfolioCode { get; set; } }
지금까지 내가 본 최고의 GroupBy 쿼리는 다음과 같습니다.
var distinctLoads = list.GroupBy(
dl => new { PeriodEnd = dl.PeriodEnd,
Component = dl.Component,
GroupCode = dl.GroupCode },
(key, data) => new {PeriodEnd = key.PeriodEnd,
Component = key.Component,
GroupCode = key.GroupCode,
PortfolioList = data.Select(d=>d.PortfolioCode)
.Aggregate((g1, g2) => g1 + "," + g2)},
null);
이 그룹이지만 여전히 중복된 그룹이 있습니다.
- GroupBy 필드를 비교하기 위해 사용자 정의 코드를 지정하려면 어떻게 해야 합니까?예를 들어 구성 요소는 Component.Code로 비교할 수 있습니다.
해결책
여기서 문제는 키 유형이 익명이라는 것입니다. 즉, 다음을 구현하는 클래스를 선언할 수 없습니다. IEqualityComparer<T>
해당 키 유형에 대해.그러는 동안 가능한 사용자 지정 방식(일반 메서드, 대리자 및 형식 추론을 통해)으로 익명 형식의 동등성을 비교하는 비교기를 작성하는 것은 별로 즐겁지 않습니다.
가장 간단한 두 가지 옵션은 아마도 다음과 같습니다.
- Equals/GetHashCode를 재정의하여 익명 유형을 "작동"하게 만듭니다.
PeriodEndDto
그리고ComponentDto
.어디에서나 사용하고 싶은 자연스러운 평등이 있다면 이것이 아마도 가장 건전한 선택일 것입니다.구현하는 것이 좋습니다IEquatable<T>
또한 - 그룹화에 익명 유형을 사용하지 마십시오. 명명된 유형을 사용하고 재정의할 수 있습니다.
GetHashCode
그리고Equals
또는 일반적인 방법으로 사용자 정의 동등 비교자를 작성할 수 있습니다.
편집하다: ProjectionEqualityComparer
정말 작동하지 않을 것입니다.그래도 비슷한 것을 작성하는 것이 가능할 것입니다. CompositeEqualityComparer
이를 통해 여러 "프로젝션 + 비교자" 쌍에서 동등 비교자를 만들 수 있습니다.하지만 익명 유형에 비해 꽤 추악합니다.
다른 팁
편집하다:
Jon Skeet이 지적했듯이 GetHashCode 구현을 잊어버렸기 때문에 이 솔루션에 대해 너무 열심히 생각하지 않는다면 이 솔루션이 실제보다 더 좋아 보입니다.Jon이 그의 대답에서 "매우 유쾌하지 않다"고 말한 것처럼 gethashcode를 구현 해야하는 것은 이러한 접근 방식을 만듭니다. 아마도 이것은 또한 (소위 "설명 할 수없는") 부재에 대한 설명이기도합니다. EqualityComparer<T>.Create()
프레임워크에서.하지 말아야 할 일의 예도 유익할 수 있으므로 참고용으로 답을 남겨 두겠습니다.
원래 답변:
에서 제안한 접근 방식을 사용할 수 있습니다. Comparer<T>.Create
.NET 4.5에 도입된 패턴(그러나 .NET 4.5에는 설명할 수 없을 정도로 없음) EqualityComparer<T>
).그렇게 하려면 DelegateEqualityComparer<T>
수업:
class DelegateEqualityComparer<T> : EqualityComparer<T>
{
private readonly Func<T, T, bool> _equalityComparison;
private DelegateEqualityComparer(Func<T, T, bool> equalityComparison)
{
if (equalityComparison == null)
throw new ArgumentNullException("equalityComparison");
_equalityComparison = equalityComparison;
}
public override bool Equals(T x, T y)
{
return _equalityComparison(x, y);
}
public static DelegateEqualityComparer<T> Create(
Func<T, T, bool> equalityComparison)
{
return new DelegateEqualityComparer<T>(equalityComparison);
}
}
그런 다음 GroupBy 메서드 주위에 래퍼를 작성하여 Func<TKey, TKey, bool>
대신하여 대리인 IEqualityComparer<TKey>
매개변수.이러한 메서드는 대리자를 다음과 같이 래핑합니다. DelegateEqualityComparer<T>
인스턴스를 생성하고 이를 해당 GroupBy 메서드에 전달합니다.예:
public static class EnumerableExt
{
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TKey, TKey, bool> equalityComparison)
{
return source.GroupBy(
keySelector,
DelegateEqualityComparer<TKey>.Create(equalityComparison);
}
}
마지막으로 호출 사이트에서 다음과 같은 표현식을 사용합니다. equalityComparison
논쟁:
(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
&& a.Component.Code.Equals(b.Component.Code)
&& a.GroupCode.Equals(b.GroupCode)