wangzq
wangzq

Reputation: 936

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

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;
        }
    }
}

Upvotes: 9

Views: 885

Answers (4)

Juls
Juls

Reputation: 688

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.

Upvotes: 1

Andrew Savinykh
Andrew Savinykh

Reputation: 26300

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.

Upvotes: 0

Tilak
Tilak

Reputation: 30718

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))))

Upvotes: 3

Tommaso Belluzzo
Tommaso Belluzzo

Reputation: 23685

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;
        }
    }
}

Upvotes: 0

Related Questions