9

Somehow following code doesn't compile in VS2010 but compiles in VS2012 without changes. The problematic line in VS2010 is

names.Select(foo.GetName)

error CS1928: 'string[]' does not contain a definition for 'Select' and the best extension method overload 'System.Linq.Enumerable.Select<TSource,TResult>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,TResult>)' has some invalid arguments.

using System;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            var foo = new Foo();
            var names = new[] {"Hello"};
            Console.WriteLine(string.Join(", ", names.Select(foo.GetName)));
        }
    }

    public class Foo
    {
    }

    static class Extensions
    {
        public static string GetName(this Foo foo, string name)
        {
            return name;
        }
    }
}
sisve
  • 19,501
  • 3
  • 53
  • 95
wangzq
  • 896
  • 8
  • 17

4 Answers4

3

Updated Answer

I have checked that the code snippet names.Select(foo.GetName) compiles in VS 2012, and does not compile on VS2010.

I donot know the reason (To be exact the new feature in C# 5.0 or .NET 4.5 or new API) that made it possible.

But following the error

The type arguments for method 'System.Linq.Enumerable.Select<TSource,TResult>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

It Seems like Enumerable.Select is not able to infer the parameter and return type of foo.GetName.

Specifying the type, code will compile.

Following are the 3 options

1 . Casting to Func<string,string>

string.Join(", ", names.Select<string,string>(foo.GetName).ToArray())

2 . Specifying types as generic parameters in Select clause

string.Join(", ", names.Select((Func<string,string>)foo.GetName).ToArray())

3 . Call Function explicitly in anonymous delegate.

 Console.WriteLine(string.Join(", ", names.Select( name => foo.GetName(name))))

But as Jon Skeet pointed in comments the above will add another function call by creating a new method.

ORIGINAL Answer

why this code doesn't compile in VS2010 with .NET 4.0?

You are not passing Parameter to the name. You are passing method name, in place of Func<T1,T2>.


Following will be compiled

Console.WriteLine(string.Join(", ", names.Select( name => foo.GetName(name))))
Tilak
  • 30,108
  • 19
  • 83
  • 131
  • 3
    Well not quite. They're not equivalent code, quite - your code will create a new method which just delegates to GetName, then create a delegate using that method... whereas the original would create a delegate referring directly to GetName. – Jon Skeet Jan 14 '13 at 14:43
  • @JonSkeet: But that's the only way this will compile, isn't it? While extension methods look like instance methods, they are not and thus can't always be used like them. – Daniel Hilgarth Jan 14 '13 at 14:52
  • @DanielHilgarth: Nope - the code as-is compiles for me and *doesn't* create an extra method. Try it yourself :) – Jon Skeet Jan 14 '13 at 15:12
  • @JonSkeet: But not in VS2010 (I just tested it) which is what the OP is asking. – Daniel Hilgarth Jan 14 '13 at 15:14
  • 1
    Sure - but my point is that your suggested solution isn't the same thing, and you haven't actually answered the question of *why* the code is valid with the C# 5 compiler but not the C# 4 compiler. – Jon Skeet Jan 14 '13 at 15:18
  • @JonSkeet: Thanks for the clarification. BTW: This answer is not from me. – Daniel Hilgarth Jan 14 '13 at 15:56
  • "[...] cannot be inferred from the usage" -- That isn't the error I get in VS2010. I get "`'string[]' does not contain a definition for 'Select' and the best extension method overload 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)' has some invalid arguments` / `Argument 2: cannot convert from 'method group' to 'System.Func'`". Which I cannot explain, since with `.Select` it does compile, and that uses the same conversion. –  Jan 14 '13 at 16:03
  • You will get the error if you do `var d = names.Select(foo.GetName)`. I have both c# 4.0 specification and c# 5.0 specification, and section 7.5.2.13 "Type inference for conversion of method groups" is exactly same at both places. Seems some compiler change. – Tilak Jan 14 '13 at 16:53
1

I had this same problem in VSS 2010. I got it fixed by changing the target framework to 3.5. then trying to build. As expected, your build will fail BUT this kick starts or resets some internal flag in VSS 2010. Now, switch back to .NET 4.0 and VSS will start building properly.

Juls
  • 658
  • 6
  • 15
  • Welcome to SO, Juls. In the future, please make sure that your answers do not suggest that users try something that didn't work for you when you have already given them the solution. – Brian Jul 25 '13 at 23:28
  • @Brian But everything worked? Switch to 3.5 - try to compile - compilation fails as expected - switch back to 4 - VS gets a kick and starts working. – GSerg Jul 25 '13 at 23:37
0
using System;
using System.Linq;

namespace ConsoleApplication1
{
    public static class Program
    {
        public static void Main()
        {
            Foo foo = new Foo();
            String[] names = new String[] { "Hello" };

            Console.WriteLine(String.Join(", ", names.Select(name => foo.GetName(name))));
        }
    }

    public class Foo { }

    public static class Extensions
    {
        public static String GetName(this Foo foo, String name)
        {
            return name;
        }
    }
}
Tommaso Belluzzo
  • 23,232
  • 8
  • 74
  • 98
  • That will work, but it doesn't answer the question of why the original code compiles with the C# 5 compiler but not the C# 4 compiler. – Jon Skeet Jan 14 '13 at 15:12
  • Probably there are minor changes in System.Core from 4.0 to 5.0. Let me check it further. – Tommaso Belluzzo Jan 14 '13 at 15:16
  • 2
    I suspect it's a *compiler* change, not a .NET library change. – Jon Skeet Jan 14 '13 at 15:17
  • 1
    Well... both Select Linq extensions are implemented as follows (source MSDN): .NET Framework -> 4.5, 4, 3.5, .NET Framework Client Profile -> 4, 3.5 SP1. I've tried the project in VS2012 switching to every compatible framework target and I never ever had that compilation error. I think you are supposing good. – Tommaso Belluzzo Jan 14 '13 at 15:24
0

It looks like it's a bug in c# 4 complier that was fixed in c# 5 compiler.

Console.WriteLine(string.Join(", ", names.Select(foo.GetName)));

is a syntactic sugar for

Console.WriteLine(string.Join(", ", names.Select(new Func<string, string>(foo.GetName))));

even though foo.GetName is an extension method. The latter works in VS2010 the former does not.

Section 6.6 of the C# Language specification, when talking about implicit conversion for a method describes the process of how the conversion happens and than says:

Note that this process can lead to the creation of a delegate to an extension method, if the algorithm of §7.6.5.1 fails to find an instance method but succeeds in processing the invocation of E(A) as an extension method invocation (§7.6.5.2). A delegate thus created captures the extension method as well as its first argument.

Based on this I would fully expect this line to work both in VS2010 and VS2012 (Since the wording does not change in the spec) but it does not. So I'm inferring this is a bug.

Here is what IL looks like when compiled in VS 2012 (comments are mine):

// pushes comma
L_0017: ldstr ", "

// pushes names variable 
L_001c: ldloc.1 

// pushes foo variable
L_001d: ldloc.0 

// pushes pointer to the extension method
L_001e: ldftn string ConsoleApplication3.Extensions::GetName(class ConsoleApplication3.Foo, string)

// pops the instance of foo and the extension method pointer and pushes delegate
L_0024: newobj instance void [mscorlib]System.Func`2<string, string>::.ctor(object, native int)

// pops the delegate and the names variable 
// calls Linq.Enumerable.Select extension method
// pops the result (IEnumerable<string>)
L_0029: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<string, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>)

// pops comma, the IEnumerable<string>
// pushes joined string
L_002e: call string [mscorlib]System.String::Join(string, class [mscorlib]System.Collections.Generic.IEnumerable`1<string>)

// pops joined string and displays it
L_0033: call void [mscorlib]System.Console::WriteLine(string)

// method finishes
L_0038: ret 

As you can see, the delegate is created out of the object instance (foo) and the method pointer, and this is exactly what should happen in VS2010 too. And it does if you specify the delegate creation new Func<string, string>(foo.GetName) explicitly.

Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158