Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ToDictionary not working as expected

Tags:

c#

linq

Given the following code, I am having trouble returning a Dictionary.

[JsonProperty]
public virtual IDictionary<Product, int> JsonProducts
{
    get
    {
        return Products.ToDictionary<Product, int>(x => x.Key, v => v.Value);
    }
}

public virtual IDictionary<Product, int> Products { get; set; }

I get the following errors..

'System.Collections.Generic.IDictionary' does not contain a definition for 'ToDictionary' and the best extension method overload 'System.Linq.Enumerable.ToDictionary(System.Collections.Generic.IEnumerable, System.Func, System.Collections.Generic.IEqualityComparer)' has some invalid arguments

cannot convert from 'lambda expression' to 'System.Func'

cannot convert from 'lambda expression' to 'System.Collections.Generic.IEqualityComparer

There is nothing special about the Product class. it is simply defined as

class Product 
{
    public virtual int Id { get; set; }
    public virtual String Name { get; set; }
}
like image 335
Ciel Avatar asked Nov 22 '10 15:11

Ciel


2 Answers

Why do you use

Products.ToDictionary<Product, int>(x => x.Key, v => v.Value)

instead of just

Products.ToDictionary(x => x.Key, v => v.Value)

?


That's because

public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector
);

Take a look to the number (3) and types of generic type parameters (Func).

which means that you need to call it:

Products.ToDictionary<KeyValuePair<Product, int>, Product, int>(x => x.Key, v => v.Value);
like image 56
abatishchev Avatar answered Nov 10 '22 13:11

abatishchev


Don't specify the generic type parameters explicitly. The types in ToDictionary<T1, T2> are not T1 = TKey and T2 = TValue (where TKey is the type of the key of the resulting dictionary and TValue is the type of the resulting value in the dictionary).

The overload of ToDictionary that accepts two generic type parameters has T = TSource and V = TKey. Here, TSource = KeyValuePair<Product, int>. Further, you are invoking the overload of ToDictionary that has two parameters. The first parameter is a map from T1 -> T2 and the second is an IEqualityComparer<T2>. But x => x.Key is not a map from KeyValuePair<Product, int> to int and v => v.Value is not an IEqualityComparer<int>.

When you don't specify the generic type parameters explicitly, the compiler inspects the types of x => x.Key and v => v.Value and looks at the various overloads of ToDictionary. There are four

  1. ToDictionary<TSource, TKey>(IEnumerable<TSource>, Func<TSource, TKey>)
  2. ToDictionary<TSource, TKey>(IEnumerable<TSource>, Func<TSource, TKey>, IEqualityComparer<TKey>)
  3. ToDictionary<TSource, TKey, TElement>(IEnumerable<TSource>, Func<TSource, TKey>, Func<TSource, TElement>)
  4. ToDictionary<TSource, TKey, TElement>(IEnumerable<TSource>, Func<TSource, TKey>, Func<TSource, TElement>, IEqualityComparer<TKey>)

Note that it can immediately rule out 1. and 4. because they have the wrong number of parameters (2 and 4 respectively whereas you are invoking an overload that needs three parameters (the third is the hidden first parameter because you are invoking an extension method)). It can rule out 2. because the last parameter can not be converted to an IEqualityComparer<T> for any T. This leaves the last overload. It is able to deduce that x => x.Key is a Func<KeyValuePair<Product, int>, Product>, that v => v.Value is a Func<KeyValuePair<Product, int>, int> and therefore that you are invoking

ToDictionary<KeyValuePair<Product, int>, Product, int>(
    IEnumerable<KeyValuePair<Product, int>>,
    Func<KeyValuePair<Product, int>, Product>,
    Func<KeyValuePair<Product, int>, int>
)

If you wanted to specify the type parameters explicitly you would have to say

Products.ToDictionary<KeyValuePair<Product, int>, Product, int>(
    x => x.Key,
    v => v.Value
);
like image 27
jason Avatar answered Nov 10 '22 14:11

jason