views:

178

answers:

3

I wanted to write an extension-method that would work on dictionaries whose values were some sort of sequence. Unfortunately, the compiler can't seem to infer the generic arguments from my usage of the method; I need to specify them explicitly.

public static void SomeMethod<TKey, TUnderlyingValue, TValue>
    (this IDictionary<TKey, TValue> dict)
    where TValue : IEnumerable<TUnderlyingValue> { }    

static void Usage()
{
    var dict = new Dictionary<int, string[]>();
    var dict2 = new Dictionary<int, IEnumerable<string>>();

    //These don't compile
    dict.SomeMethod();
    SomeMethod(dict); // doesn't have anything to do with extension-methods
    dict2.SomeMethod(); // hoped this would be easier to infer but no joy


    //These work fine
    dict.SomeMethod<int, string, string[]>();
    dict2.SomeMethod<int, string, IEnumerable<string>>();
}

I realize that type inference isn't an exact science, but I was wondering if there's some fundamental 'rule' I'm missing here - I'm not familiar with the details of the spec.

  1. Is this a shortcoming of the inference process or is my expectation that the compiler should "figure it out" unreasonable in this case (ambiguity perhaps)?
  2. Can I change the method's signature in a way that would make it equally functional yet 'inferrable'?
+1  A: 

Why not leave out the type of the IEnumerable?

public static void SomeMethod<TKey, TValue>
(this IDictionary<TKey, TValue> dict)
where TValue : IEnumerable { }    
Pieter
Because then you won't (short of reflection) have access to the type of the underlying values - all you will know is that each `Value` is a sequence of *something*, but not what that something is.
AakashM
You could cast TValue to IEnumerable<TValue> because you know it's of the type.
Pieter
+3  A: 

C# type inference doesn't work off of constraints or return values. So you'll have slightly better luck with

public static void SomeMethod<TKey, TUnderlyingValue>
    (this IDictionary<TKey, IEnumerable<TUnderlyingValue>> dict)
  { }

This will work if you declare the param as new Dictionary< string, IEnumerable<int>>(), but not if you declare it new Dictionary<string, List<int>>().

I do have to say, that the way I read section 7.5.2 of the c# spec, it seems that since List<int> implements IEnumerable<int>, the type inference of TUnderlyingValue should work. However, that section isn't exactly straightforward to understand. I assume it does not work through the multiple "layers", since SomeMethod<T>(IEnumberable<T> val){} would work just fine calling it with SomeMethod(new List<string>()). I don't specifically see anything in the spec that deals with resolving a type where U = Ca<Va, Cb<Vb>>, so perhaps inference at that level is not defined.

Philip Rieck
+1 @Philip Rieck: Thanks. I'm aware of this workaround, but it doesn't solve the general case, which is what I'm interested in.
Ani
@Ani see edits - I'm not sure that the type inference defined and implemented in c# 4.0 supports that level of inference at all.
Philip Rieck
+11  A: 

I realize that type inference isn't an exact science

I'm not sure I agree. The spec is quite detailed.

I was wondering if there's some fundamental 'rule' I'm missing here

The fundamental rule that you're missing is probably that constraints are not part of the signature. Type inference works off of the signature.

There are in my opinion good reasons for that design decision. However, many people believe that I am morally wrong for believing that there are good reasons for that design decision. If you're interested in reading what feels like several million words on the topic of whether I'm right or wrong, see my article on the subject and the hundred or so comments telling me I'm wrong:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

Is this a shortcoming of the inference process?

Arguably, yes. In my opinion, it is a reasonable choice given competing design requirements. (Those being "do what the user meant" and "give errors when things look ambiguous".)

is my expectation that the compiler should "figure it out" unreasonable in this case?

No. You seem like a reasonable person, and your expectation appears to be based on good reasoning. However, it is entirely possible to have a reasonable expectation that nevertheless is unmet. This would be one of those cases.

Can I change the method's signature in a way that would make it equally functional yet 'inferrable'?

That's going to be difficult, since the generic Dictionary type is not covariant or contravariant in its conversions. The concept you want to capture is not easily expressed in the type system in a manner that affords inference.

If you prefer using languages with more advanced type inference, consider using F#. If you prefer languages that skew towards "do what the user meant" rather than "report errors on ambiguity", consider using VB.

Eric Lippert
Brilliant, thanks - "constraints are not part of the signature" is what I was missing, and I have to wrap my head around that. On another note, what I meant by "not an exact science" is that it would be possible for two different but valid inference algorithms (in general, not specific to the spec) to infer the type arguments differently, yet both be justified in their choice. Do you agree?
Ani
@Ani: Absolutely, there are many different possible type inferencers. We made a deliberate choice to not use Hindley-Milner-style type inference in C#, for example.
Eric Lippert
@Eric: What's the name of the type inference that you guys used? Just wondering if it's invented by someone else like the Hindley-Milner-style type inference (I assume).
Joan Venge
@Joan: We don't have a name for it. It was a joint effort of the whole C# design committee. In particular, Anders, Mads, and I worked heavily on it. Erik Meijer and some of the Microsoft Research team members also contributed many useful ideas, particularly in the area of how to resolve cyclic situations. I do not recall who all is on the patent declaration.
Eric Lippert