views:

80

answers:

3

I am using using Microsoft .NET Framework 4.0.

I have run into this using Aggregate on a Dictionary<T, List<T>> to extract the set of type T values used across all type List<T> lists in the dictionary. Here is the simplest case I could come up with that exhibits the same behaviour.

First, as the documentation states, the following does work:

var set = new HashSet<int>();
var list = new LinkedList<int>();
var newSet = set.Union(list);

That is, I can call Union on a HashSet with a List as the argument (since it implements IEnumerable).

Whereas, the equivalent expression within a Func argument of the LINQ Aggregate expression produces an error (precompiler at least):

new List<int>[] { new List<int>() }.Aggregate(new HashSet<int>(), (acc, list) => acc.Union(list));

It expects the argument of Union to be HashSet, and will cooperate if it is given one, contrary to its behaviour outside LINQ/Func expressions.

The real world example I was using when I came across the problem was:

public AdjacencyListGraph(Dictionary<TVertex, LinkedList<TVertex>> adjacencyList)
{
    var vertices = adjacencyList.Aggregate(new HashSet<TVertex>(),
    (vertices, list) => vertices.Union(list.Value));
}

Which complains that it cannot convert IEnumerable<TVertex> to HashSet<TVertex>...

+2  A: 

The problem here is in your understanding of the Select method. The lambda passed in does not recieve the list but instead elements of the list. So the variable you've named list is in fact of type int which is not compatible with Union.

Here is a more explicit example of what you're trying to do

new List<int>().Select( (int list) => new HashSet<int>().Union(list));

With the type infererence removed it's much clearer why this doesn't work.

JaredPar
You're quite right, I am using Select wrongly in the above example, it should have read something like this: new List<int>[] {new List<int>()}.Select(list => new HashSet<int>().Union(list));In my real world case you van see that list is a genuine list.
silasdavis
A: 
new List<int>().Select(list => new HashSet<int>().Union(list));

I think you would expect to use SelectMany here, unless you want the result to be IEnumerable<IEnumerable<T>>.

Richard
A: 

The problem actually lies in trying to replace the accumulator type of HashSet with the IEnumerable, .Union method doesn't add items to HashSet, but returns the new IEnumerable on the resulting union.

You should change your code to the following:

    // select from keys
    var vertices = new HashSet<TVertex>(adjacencyList.Keys);

or

    // select from values
    var vertices = new HashSet<TVertex>(adjacencyList.SelectMany(dictEntry => dictEntry.Value));
Grozz
Your first example would not produce what my expression is trying to achieve. I am looking to take the union of all of the adjacencyList's Values, which are lists, not to get the eet of the indices for those lists.
silasdavis
That's why I added the second. Your intentions couldn't be deduced from the incorrect code.
Grozz
Sorry I was just about to reply, the second does do what I want, thanks.
silasdavis
However I'm not sure why I can't do what I did originally. I intended to replace the accumulator... like in a ruby inject
silasdavis
Ah... The following does work: var vs = adjacencyList.Aggregate((IEnumerable<TVertex>) new HashSet<TVertex>(), (vertices, list) => vertices.Union(list.Value));
silasdavis
I think it is quite poor that I have to cast a HashSet to an IEnumerable to make this work. The code is unambiguous. Thanks for your help though, you correctly identified the issue.
silasdavis