Why do I have to explicitly specify my type arguments for Func parameters?

I am writing a simple Memoize helper that allows caching method results instead of computing them every time. However, when I try to pass a method into Memoize, the compiler can't determine the type arguments. Aren't they obvious from my method signature? Is there a way around this?

Sample code:

using System;
using System.Collections.Concurrent;

public static class Program
{
    public static Func<T, V> Memoize<T, V>(Func<T, V> f)
    {
        var cache = new ConcurrentDictionary<T, V>();
        return a => cache.GetOrAdd(a, f);
    }

    // This is the method I wish to memoize
    public static int DoIt(string a) => a.Length;        

    static void Main()
    {
        // This line fails to compile (see later for error message)
        var cached1 = Memoize(DoIt);

        // This works, but is ugly (and doesn't scale to lots of type parameters)
        var cached2 = Memoize<string, int>(DoIt);
    }
}

Error message:

error CS0411: The type arguments for method 'Program.Memoize<T, V>(Func<T, V>)'
cannot be inferred from the usage. Try specifying the type arguments explicitly.
Jon Skeet
people
quotationmark

Isn't DoIt() signature compatible with Func<string, int>?

Yes it is. It's fine to convert it to that specific type, like this for example:

Func<string, int> func = DoIt;
var cachedDoit = Memoize(func);

The problem you're running into is that type inference basically doesn't work particularly well with method group conversions. When you pass DoIt as an argument, that's a method group. In your case it only refers to a single method, but it could refer to multiple methods, with different signatures... and that complicates things.

I often see this come up with LINQ, where I'd like to call foo.Select(SomeMethodGroup), but type inference fails. There's some support for method groups within type inference, but it's not everything we might want it to be.

This isn't a matter of the C# team being lazy... type inference is hugely complicated, and any change is really fraught with danger in terms of backward compatibility. It's in section 7.5.2 of the C# 5 specification if you want to have a look - but frankly that's part of the spec where I get lost really quickly.

people

See more on this question at Stackoverflow