问题
So I've looked through about 20 examples on this on SO and elsewhere, but haven't found one which covers what I'm trying to do. This - Can I specify my explicit type comparator inline? - looks like what I need, but doesn't go far enough (or I don't understand how to take it further).
- I have a List of LoadData, the LoadData object has fields of both reference and value types
- Need to group on a mixture of ref and value fields, project the output to an anonymous type
Need (I think) to provide a custom IEqualityComparer to specify how to compare the GroupBy fields, but they are an anonymous type
private class LoadData { public PeriodEndDto PeriodEnd { get; set; } public ComponentDto Component { get; set; } public string GroupCode { get; set; } public string PortfolioCode { get; set; } }
The best GroupBy query I have going so far:
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);
This groups, but there are still duplicates.
- How can I specify custom code to compare the GroupBy fields? For example, the Components could be compared by Component.Code.
回答1:
The problem here is that your key type is anonymous, which means you can't declare a class that implements IEqualityComparer<T>
for that key type. While it would be possible to write a comparator which compared anonymous types for equality in a custom manner (via a generic method, delegates and type inference), it wouldn't be terribly pleasant.
The two simplest options are probably:
- Make the anonymous type "just work" by overriding Equals/GetHashCode in
PeriodEndDto
andComponentDto
. If there's a natural equality you'd want to use everywhere, this is probably the sanest option. I'd recommend implementingIEquatable<T>
as well - Don't use an anonymous type for grouping - use a named type, and then you can either override
GetHashCode
andEquals
on that, or you could write a custom equality comparer in the normal way.
EDIT: ProjectionEqualityComparer
wouldn't really work. It would be feasible to write something similar though - a sort of CompositeEqualityComparer
which allowed you create an equality comparer from several "projection + comparer" pairs. It would be pretty ugly compared with the anonymous type though.
回答2:
EDIT:
As Jon Skeet points out, this solution seems better than it is, if you don't think too hard about it, because I have forgotten to implement GetHashCode. Having to implement GetHashCode makes this approach, as Jon says in his answer, "not terribly pleasant." Presumably, this is also the explanation for the (so-called "inexplicable") absence of EqualityComparer<T>.Create()
in the framework. I'll leave the answer for reference, as examples of what not to do, can be instructive as well.
ORIGINAL ANSWER:
You could use the approach suggested by the Comparer<T>.Create
pattern introduced in .NET 4.5 (but inexplicably absent in EqualityComparer<T>
). To do so, create a DelegateEqualityComparer<T>
class:
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);
}
}
Then write wrappers around the GroupBy methods to accept a Func<TKey, TKey, bool>
delegate in place of the IEqualityComparer<TKey>
parameter. These methods wrap the delegate in a DelegateEqualityComparer<T>
instance, and pass that on to the corresponding GroupBy method. Example:
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);
}
}
Finally, at your call site, you would use something like this expression for the equalityComparison
argument:
(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
&& a.Component.Code.Equals(b.Component.Code)
&& a.GroupCode.Equals(b.GroupCode)
来源:https://stackoverflow.com/questions/12703186/linq-groupby-on-multiple-ref-type-fields-custom-equalitycomparer