1

I have been playing with type inference in C# and I have read this thread. As stated in the thread, type inference does not work for generic delegates (when you pass a method to this delegate parameter without any explicit casting) like Func, Action and etc. Then I would like to try LINQ operators like SelectMany with .net Func classes instead of lambda expressions in order to see the result. However, it was not result I expected.

I created a test class named L1 which contains a list of strings in order to use (flatten) in SelectMany. In addition to that I created a test method in order to pass instead of Func type parameter.

public class L1
{
    private L1() {}

    public L1(string m)
    {
        Isim = m;
        numbers = new List<string> { "1", "2", "3", "4", "5" };
    }

    public List<string> numbers; 
    public string Isim { get; private set; }
}
private static IEnumerable<int> a(L1 k)
{
    return Enumerable.Empty<int>();
}

When I created an extension method like the one below, C# compiler cannot inference the type of the generic parameters.

public static void Test<T1, T2>(this L1 l, Func<T1, IEnumerable<T2>> a)
{
}

(new L1("test")).Test(a);

It gives 'error CS0411: The type arguments for method 'EnumSand.Test(L1, Func>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.' as I expexted.

However, when I use this method a in LINQ SelectMany extension method, compiler does not give any error and it works.

L1[] l1 = new L1[5] {
                        new L1("one"), new L1("two"), 
                        new L1("three"), new L1("four"), 
                        new L1("five")
                    };

var slack = l1.SelectMany(a);

foreach (var t in slack)
{
    Console.WriteLine(t);
}

So, what is the difference between these cases and how it is possible in SelectMany to make a good type inference?

Community
  • 1
  • 1
Deniz
  • 858
  • 10
  • 31

1 Answers1

2

The problem is here:

public static void Test<T1, T2>(this L1 l, Func<T1, IEnumerable<T2>> a)

T2 is bound to int but T1 may be anything, that's why it cannot be inferred.

You can either change second parameter to Func<L1, IEnmuerable<T2>> and remove T1 completely OR change this L1 to this T1.

EDIT: consider the situation when you have following methods:

private static IEnumerable<int> a(L1 k)
{
    return Enumerable.Empty<int>();
}

private static IEnumerable<int> a(string k)
{
    return Enumerable.Empty<int>();
}

Can you now say which a method should be used for (new L1("test")).Test(a); use?

synek317
  • 749
  • 2
  • 7
  • 22
  • Do you mean compiler can inder from return type, but not from arguments? `Func> selector` in `SelectMany` signature already knows `TSource` due to the extension method rules, and return type inference is enough in that case? – Deniz Mar 31 '16 at 22:05
  • @Deniz, I agree with this answer. It does not mean that compiler can infer from return type, but not from arguments. Not sure, why you would think that. – Andrew Savinykh Mar 31 '16 at 22:21
  • @AndrewSavinykh, I tried changing T1 into L1, same result. – Deniz Mar 31 '16 at 22:26
  • @Deniz, changing T1 into L1 works. Double-check your code. You must have made a mistake. – Andrew Savinykh Mar 31 '16 at 22:29
  • @Deniz, this definitely works: `public static void Test(this T1 l, Func> a)`. The thread you linked in original question explain why very well just read it carefully. – synek317 Mar 31 '16 at 22:34
  • @Yes, it works, but in `Func>` T2 (IEnumerable - return type) can be inferred unlike parameters, right? – Deniz Mar 31 '16 at 22:38
  • https://blogs.msdn.microsoft.com/ericlippert/2007/11/05/c-3-0-return-type-inference-does-not-work-on-method-groups/ – Deniz Mar 31 '16 at 22:44
  • @Deniz please take a look at my updated answer and example. – synek317 Mar 31 '16 at 23:05
  • @synek317, yes you are right, method groups - as you mentioned in overloading example - have no .NET type which, therefore a type inference is not possible. However, my last question was how T2 is inferred. Your answer is correct, but please see the link in my previous comment. So, T2 inferred from the return type of `Func`, which is `IEnumerable` for our case? – Deniz Mar 31 '16 at 23:17
  • Unfortunately I cannot answer you. Maybe in c# 3.0 it was not possible, c# 4.0 fully allows inferring type from return type, e.g. this `var cities = customers.Select(GetCity);` (from msdn article you linked) works just fine. – synek317 Apr 01 '16 at 08:14