Ciel
Ciel

Reputation: 17772

ToDictionary not working as expected

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

Upvotes: 12

Views: 9088

Answers (3)

abatishchev
abatishchev

Reputation: 100328

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

Upvotes: 18

jason
jason

Reputation: 241741

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

Upvotes: 1

Dan Bryant
Dan Bryant

Reputation: 27515

If you're not actually cloning the Product instances, you can just do:

public virtual IDictionary<Product, int> JsonProducts
{
    get
    {
        return new Dictionary(Products);
    }
}

Upvotes: 0

Related Questions