2

When I run this program I get two errors on the line containing fn(3,4)

Argument 1: cannot convert from 'int' to T

Argument 2: cannot convert from 'int' to T

I thought type T would be int, as specified by the lambda. But if so then why the conversion errors? Am I misunderstanding something?

class Program
{
    static void DoStuff<T>(Func<T, T, T> fn)
    {
        Console.WriteLine(fn(3, 4));
    }

    static void Main()
    {
        DoStuff((int x, int y) => x + y);
    }
}

This works, if I change the parameters to accept the ints as arguments:

class Program
{
    static void DoStuff<T>(T x, T y, Func<T, T, T> fn)
    {
        Console.WriteLine(fn(x, y));
    }

    static void Main()
    {
        DoStuff(3, 4, (int x, int y) => x + y);
    }
}

I come from a C++ background, so trying to get my head around what works and what doesn't in C#

PowerApp101
  • 1,798
  • 1
  • 18
  • 25
  • 1
    This isn't allowed because for something like `DoStuff` it wouldn't be valid. Generics don't work like C++ templates, if that's what you were thinking of. –  Feb 13 '14 at 14:55

2 Answers2

8

Inside the DoStuff<T> method, the actual type of T is unknown; your code passes ints to the function, assuming that T is int, but T could actually be anything, like string, so you would be passing ints to a function that accepts only strings.


RE "I come from a C++ background, so trying to get my head around what works and what doesn't in C#":

C# generics look similar to C++ templates, but they're actually quite different.

  • In C++, templates are instantiated at compile time based on usage; if you use the template method with 3 different types for T, the compiler will generate 3 different methods. The body of the template method doesn't have to be valid for any T, as long as it's valid for the actual usage cases. (sorry if my explanation isn't perfectly accurate; I don't know C++ very well)

  • In C#, there is no compile-time generation based on usage; the compiler only generates 1 generic method, and the actual runtime method for a given T is generated by the runtime. To ensure this will work, the C# compiler must ensure that the method body will be valid for any T (or if you specified constraints for T, any T that matches the constraints). That's why you're getting an error in your first snippet: the body of the generic method would only be valid if T were int.

I suggest you read the answers to this question for a more detailed explanation.

Community
  • 1
  • 1
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Thanks. I've edited my post to show a working snippet...can you explain why that works? – PowerApp101 Feb 13 '14 at 15:07
  • @20thCenturyBoy, that works because the arguments are specified are the call site, where the actual type of `T` is known. – Thomas Levesque Feb 13 '14 at 15:23
  • @20thCenturyBoy, I updated my answer to explain the difference between C++ templates and C# generics – Thomas Levesque Feb 13 '14 at 15:29
  • I guess where I'm getting befuddled is that to me, it looks like the first snippet also specifies the type at the call site, in the lambda expression itself which clearly specifies two int arguments and an int return. I guess I need to read up a bit more! – PowerApp101 Feb 13 '14 at 15:46
  • 1
    @20thCenturyBoy, the first snippet specifies the type at the call site, but the implementation doesn't know that, so you can't assume in the body of `DoStuff` that `T` is of type `int`. – Thomas Levesque Feb 13 '14 at 17:32
1

No need to use generics here. Change

static void DoStuff<T>(Func<T, T, T> fn)

to

static void DoStuff(Func<int, int, int> fn)
Henrik
  • 23,186
  • 6
  • 42
  • 92
  • Yeah I know that works, but I wanted to understand why the generic version couldn't infer type T as int. – PowerApp101 Feb 13 '14 at 14:56
  • @20thCenturyBoy Because generic type inference happens at the call site, not in the method definition. It wouldn't make sense to infer the parameter types at the method definition, because then the method wouldn't be generic in the first place. – Kyle Feb 13 '14 at 15:27
  • @Kyle I see what you mean. I just thought that the type information contained in the lambda at the call site would be sufficient to infer type T, i.e. the lambda specifies ints. – PowerApp101 Feb 13 '14 at 15:39
  • 1
    @20thCenturyBoy The arguments in the lambda *are* sufficient for the compiler to infer the type parameter at the call site of the method (which is what's going on in your second example), however the compiler doesn't get that far in your case. Inside the method definition, you're trying to invoke the delegate with int arguments, but the compiler doesn't know what the arguments actually will be since the method could later be invoked with *any* type. So the method itself can't be compiled. – Kyle Feb 13 '14 at 15:55