IEqualityComparer for Annoymous Type

那年仲夏 提交于 2021-02-06 12:27:14

问题


Firstly I have seen IEqualityComparer for anonymous type and the answers there do not answer my question, for the obvious reason that I need an IEqualityComparer not and IComparer for use with Linq's Distinct() method. I have checked the other answers too and these fall short of a solution...

The Problem

I have some code to manipulate and pull records in from a DataTable

var glext = m_dtGLExt.AsEnumerable();
var cflist =
    (from c in glext
     orderby c.Field<string>(m_strpcCCType), 
             c.Field<string>(m_strpcCC), 
             c.Field<string>(m_strpcCCDesc),
             c.Field<string>(m_strpcCostItem)
     select new
     {
        CCType = c.Field<string>(m_strpcCCType),
        CC = c.Field<string>(m_strpcCC),
        CCDesc = c.Field<string>(m_strpcCCDesc),
        CostItem = c.Field<string>(m_strpcCostItem)
     }).Distinct();

but I need the distinct method to be case insensitive. What is throwing me here is the use of anonymous types.

Attempted Solution 1

If I had SomeClass which had concrete objects I could obviously do

public class SumObject
{
    public string CCType { get; set; }
    public string CC { get; set; }
    public string CCDesc { get; set; }
    public string CostItem { get; set; }
}

I could obviously do this

List<SumObject> lso = new List<SumObject>() 
{
    new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Rooney", CostItem = "I477" },
    new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Zidane", CostItem = "I677" },
    new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Falcao", CostItem = "I470" },
};
var e = lso.Distinct(new SumObjectComparer()); // Great :]

where

class SumObjectComparer : IEqualityComparer<SumObject>
{
    public bool Equals(SumObject x, SumObject y)
    {
        if (Object.ReferenceEquals(x, y)) 
            return true;
        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            return false;
        return x.CCType.CompareNoCase(y.CCType) == 0 && 
               x.CC.CompareNoCase(y.CC) == 0 && 
               x.CCDesc.CompareNoCase(y.CCDesc) == 0 && 
               x.CostItem.CompareNoCase(y.CostItem) == 0;
    }

    public int GetHashCode(SumObject o)
    {
        if (Object.ReferenceEquals(o, null)) 
            return 0;
        int hashCCType = String.IsNullOrEmpty(o.CCType) ? 
            0 : o.CCType.ToLower().GetHashCode();
        int hashCC = String.IsNullOrEmpty(o.CC) ? 
            0 : o.CC.ToLower().GetHashCode();
        int hashCCDesc = String.IsNullOrEmpty(o.CCDesc) ? 
            0 : o.CCDesc.ToLower().GetHashCode();
        int hashCostItem = String.IsNullOrEmpty(o.CostItem) ? 
            0 : o.CostItem.ToLower().GetHashCode();
        return hashCCType ^ hashCC ^ hashCCDesc ^ hashCostItem;
    }
}

However, the use of anonymous types in the above Linq query are throwing me.

Attempted Solution 2

To attempt another solution to this (and because I have the same issue elsewhere) I generated the following generic comparer class

public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> compareFunction;
    Func<T, int> hashFunction;

    public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction)
    {
        this.compareFunction = compareFunction;
        this.hashFunction = hashFunction;
    }

    public bool Equals(T x, T y) { return compareFunction(x, y); }
    public int GetHashCode(T obj) { return hashFunction(obj); }
}

so that I could attempt to do

var comparer = new GenericEqualityComparer<dynamic>(
    (x, y) => { /* My equality stuff */ }, 
    o => { /* My hash stuff */ });

but this casts the returned value as IEnumerable<dynamic> which in turn effects my forthcoming use of cflist, so that in a following query the join fails.

 var cf = 
    (from o in cflist
     join od in glext
     on new { o.CCType, o.CC, o.CCDesc, o.CostItem } equals new
     {
        CCType = od.Field<string>(m_strpcCCType),
        CC = od.Field<string>(m_strpcCC),
        CCDesc = od.Field<string>(m_strpcCCDesc),
        CostItem = od.Field<string>(m_strpcCostItem)
     }
     into c
     select new { ... }

I don't want to get into ugly casting to and from IEnumerable<T>s due to the heavy use of this code...

Question

Is there a way I can create my an IEquailityComparer for my anonymous types?

Thanks for your time.


回答1:


Is there a way I can create my an IEquailityComparer for my anonymous types?

Sure. You just need to use type inference. For example, you could have something like:

public static class InferredEqualityComparer
{
    public static IEqualityComparer<T> Create<T>(
        IEnumerable<T> example,
        Func<T, T, bool> equalityCheck,
        Func<T, int> hashCodeProvider)
    {
        return new EqualityComparerImpl<T>(equalityCheck, hashCodeProvider);
    }

    private sealed class EqualityComparerImpl<T> : IEqualityComparer<T>
    {
        // Implement in the obvious way, remembering the delegates and
        // calling them appropriately.
    }
}

Then:

var glext = m_dtGLExt.AsEnumerable();
var query = from c in glext
            orderby ...
            select new { ... };
var comparer = InferredEqualityComparer.Create(query,
    (x, y) => { ... },
    o => { ... }
);
var distinct = query.Distinct(comparer);

Basically the first parameter to the method is just used for type inference, so that the compiler can work out what type to use for the lambda expression parameters.

You could create the comparer ahead of time by creating a sample of the anonymous type:

var sample = new[] { new { ... } };
var comparer = InferredExqualityComparer.Create(sample, ...);
var distinct = (... query here ... ).Distinct(comparer);

but then any time you change the query you've got to change the sample too.




回答2:


This post may get what you want. Although for .NET 2.0 it also works for newer versions (see the bottom of this post for how to achieve this). In contrast to Jon Skeets solution we won´t use a factory-method like create. But this is only syntactic sugar I think.



来源:https://stackoverflow.com/questions/25934419/iequalitycomparer-for-annoymous-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!