【问题标题】:IEqualityComparer on different types不同类型的 IEqualityComparer
【发布时间】:2017-10-13 21:44:13
【问题描述】:

给定两个类

class Contract
{
    public int ID {get; set;}
    // . . . 
}

class DBContract
{
    public int FromID {get; set;}
    // . . . 
}

还有两个 IEnumerables

IEnumerable<Contract> ValidContracts = Application.GetContracts(//. . .
IEnumerable<DBContract> ExportedContracts = DBAdapter.GetRows(// . . .

我需要找到这些 IEnumerables 的交集。但是如果 IEqualityComparer 只有一个类型参数,我该如何实现呢?

【问题讨论】:

  • 你可以让他们实现一个像IDBContract这样的通用接口,然后提供一个IEqualityComparer&lt;IDBContract&gt;
  • 您不必实现 IEqualityComparer。
  • @TimSchmelter Contract 是一个库类
  • 结果应该是什么? ContractDBContract,还有别的吗?
  • @OfirWinegarten 这个问题让我觉得我的问题不正确。实际上我寻求Contract作为结果

标签: c# linq


【解决方案1】:

为什么不改用WhereAny 呢?它的性能不如Intersect,但它允许您以您想要的方式过滤:

var list = ExportedContracts.Where(ec => ValidContracts.Any(vc => vc.ID == ec.FromID));

您不能在此处使用IEqualityComparer,因为该对象没有任何接口或基类(object 除外)。

【讨论】:

  • 在此之前,我会将ValidContracts 转换为数组或列表。
  • 是的,这可能是个好主意。甚至是提高性能的字典。
  • @xanatos 它实际上是一个自定义类,实现了IList, ICollection ans IEnumebable
【解决方案2】:

为了对混合类型的集合进行操作,IEqualityComparer&lt;T&gt; 的类型参数 T 必须接受集合中所有类型的共同祖先。

由于ContractDbContract看似无关,且不共享一个通用接口,所以需要使用object作为通用基类。

这对于您的目的来说可能太复杂了:也许您可以通过ID 实现交集,如下所示:

var commonIds = new HashSet<int>(contracts.Select(c => c.Id));
commonIds.IntersectWith(dbContracts.Select(dbc => dbc.FromId));

现在commonIds 拥有IDs 您需要的对象。在两边运行简单的Wheres 会产生两个静态类型的交叉部分:

var commonContracts = contracts.Where(c => commonIds.Contains(c.Id));
var commonDbContracts = dbContracts.Where(dbc => commonIds.Contains(dbc.FromId));

【讨论】:

    【解决方案3】:

    我会为Contract/DBContract 创建一个临时容器:

    public class ContractDbContract
    {
        public Contract Contract;
        public DBContract DBContract;
    
        public int ID 
        {
            get
            {
                return Contract?.ID ?? DBContract.FromID;
            }
    
        }
    }
    
    IEnumerable<Contract> ValidContracts = Application.GetContracts(//. . .
    IEnumerable<DBContracts> ExportedContracts = DBAdapter.GetRows(// . . .
    
    var validContracts2 = ValidContracts.Select(x => new ContractDbContract { Contract = x });
    var exportedContracts2 = ExportedContracts.Select(x => new ContractDbContract { DBContract = x });
    

    现在你可以随意交叉validContracts2exportedContracts2,然后很容易从ContractDBContract中“提取”Contract/DBContract

    【讨论】:

      【解决方案4】:

      如果他们不能让他们实现与IDBContract 相同的接口并提供自定义IEqualityComparer&lt;IDBContract&gt;,则不能使用Intersect,但可以使用Enumerable.Join

      var commonContracts = from c in ValidContracts
                            join ec in ExportedContracts on c.ID equals ec.FromID
                            select new { contract = c, exportedContract = ec };
      

      Join 也使用基于集合的方法,因此它比Where 更有效:

      Why is LINQ JOIN so much faster than linking with WHERE?

      【讨论】:

        【解决方案5】:

        您可以自己实现Intersect,但不使用HashSet,例如:

        public static IEnumerable<TFirst> Intersect<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second,
            Func<TFirst, TSecond, bool> comparerFunc)
        {
            return first.Where(firstItem => second.Any(secondItem => comparerFunc(firstItem, secondItem)));
        }
        

        并像这样使用它:

        IEnumerable<Contract> ValidContracts = Application.GetContracts(//. . .
        IEnumerable<DBContract> ExportedContracts = DBAdapter.GetRows(// . . .
        
        var intersect = ValidContracts.Intersect(ExportedContracts, (contract, dbContract) => contract.ID == dbContract.FromID);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多