8

Given two classes

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

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

And two IEnumerables

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

I need to find intersection of theese IEnumerables. But how do I implement IEqualityComparer, if it only has one type argument?

Dmitry Pavlushin
  • 612
  • 8
  • 23

5 Answers5

7

Why not use Where and Any instead? It is not as performant as Intersect, but it allows you to filter the way you want:

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

You can't use IEqualityComparer here since the object doesn't have any interface or base class (except object).

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
7

In order to operate on a collection of mixed types an IEqualityComparer<T>'s type argument T must accept a common ancestor of all types in the collection.

Since Contract and DbContract appear unrelated, and do not share a common interface, you need to use object as the universal base class.

This may be too complicated for your purposes: perhaps you could implement intersection by ID as follows:

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

Now commonIds has IDs of the objects that you need. Running simple Wheres on both sides would produce two statically-typed parts of the intersection:

var commonContracts = contracts.Where(c => commonIds.Contains(c.Id));
var commonDbContracts = dbContracts.Where(dbc => commonIds.Contains(dbc.FromId));
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
2

I would create a temporary container for 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 });

Now you can intersect validContracts2 and exportedContracts2 however you want, and then it is easy to "extract" the Contract/DBContract from the ContractDBContract

xanatos
  • 109,618
  • 12
  • 197
  • 280
1

If they can't let them implement the same interface like IDBContract and provide a custom IEqualityComparer<IDBContract> you can't use Intersect but you could use 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 also uses a set based approach so it's more efficient than Where:

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

Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

You could implement Intersect yourself, but without using HashSet, something like:

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

And use it like:

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

var intersect = ValidContracts.Intersect(ExportedContracts, (contract, dbContract) => contract.ID == dbContract.FromID);
Konrad
  • 6,385
  • 12
  • 53
  • 96