Share via


Visual C# Breaking Changes in Visual Studio 2012

The following table lists changes in Visual C# in Visual Studio 2012 that might either prevent an application that was created in Visual C# in Visual Studio 2010 from compiling or change the run-time behavior of such an application.

Category

Issue

Description

Lambda expressions

You can use the iteration variable of a foreach statement in a lambda expression that’s contained in the body of the loop.

The use of a foreach iteration variable in a nested lambda expression no longer produces unexpected results. The following example uses the variable word in a lambda expression.

static void Main()
{
    var methods = new List<Action>();
    foreach (var word in new string[] { "hello", "world" })
    {
        methods.Add(() => Console.Write(word + " "));
    }

    methods[0]();
    methods[1]();
}

// Output in Visual Studio 2012: 
// hello world

// Output in Visual Studio 2010: 
// world world

LINQ expressions

You can use the iteration variable of a foreach statement in a LINQ expression that’s contained in the body of the loop.

The use of a foreach iteration variable in a LINQ expression no longer produces unexpected results. The following example uses the variable number in a LINQ query.

static void Main()
{
    var lines = new List<IEnumerable<string>>(); 
    int[] numbers = { 1, 2, 3 };
    char[] letters = { 'a', 'b', 'c' };

    foreach (var number in numbers)
    {
        var line = from letter in letters
                   select number.ToString() + letter;

        lines.Add(line);
    }

    foreach (var line in lines)
    {
        foreach (var entry in line)
            Console.Write(entry + " ");
        Console.WriteLine();
    }
}
// Output in Visual Studio 2012: 
// 1a 1b 1c
// 2a 2b 2c
// 3a 3b 3c

// Output in Visual Studio 2010: 
// 3a 3b 3c
// 3a 3b 3c
// 3a 3b 3c

Named arguments

Side effects from named and positional arguments in a method call now occur from left to right in the argument list.

Side effects from named and positional arguments that are combined in a method call are now produced from left to right in the calling statement's argument list. In the following example, TestMethod is called by using a combination of named and positional arguments in different orders.

class Program
{
    static void Main(string[] args)
    {
        TestMethod(WriteLetter("A"), b: WriteLetter("B"), c: WriteLetter("C"));
        TestMethod(WriteLetter("A"), c: WriteLetter("C"), b: WriteLetter("B"));
    }

    static int WriteLetter(string letter)
    {
        Console.Write(letter + " ");
        return 1;
    }

    static void TestMethod(int a, int b, int c)
    { }

    // Output in Visual Studio 2012:
    // A B C A C B

    // Output in Visual Studio 2010:
    // B C A C B A
}

Overload resolution

Overload resolution has been improved for calls that use named arguments to access methods that contain params parameters.

When more than one resolution candidate is found, overload resolution prefers the most specific type match for named arguments. Parameters for which arguments aren’t required or provided in the call are considered only when the type matches in overload candidates are equally good.

In the following example, string is a better type than object for p2. Therefore, the version of ExampleMethod in which parameter p2 is defined as a string should be chosen, even though it has a third params parameter.

class Program
{
    static void Main(string[] args)
    {
        ExampleMethod(p2: "");
    }

    public static void ExampleMethod(string p1 = null, object p2 = null)
    {
        Console.WriteLine("ExampleMethod: p2 is object");
    }
    public static void ExampleMethod(string p2 = null, object p1 = null, params int[] p3)
    {
        Console.WriteLine("ExampleMethod: p2 is string");
    }
}

// Output in Visual Studio 2012:
// ExampleMethod: p2 is string

// Output in Visual Studio 2010:
// ExampleMethod: p2 is object

Overload resolution

Overload resolution is improved for calls where the algorithm must choose between a Func<object> parameter and a Func parameter that has a different type parameter (e.g., string or int?) for a Func<dynamic> argument.

In the following example, the call to CandidateMethod that sends a Func<dynamic> argument has two resolution candidates. The corresponding parameter in one of the candidates is Func<object>, and the corresponding parameter in the other is Func<string>.

The overload candidate that has a Func<object> parameter should be chosen because object and dynamic are considered to be equivalent. Therefore, an identity conversion exists not only between dynamic and object but also between constructed types Func<dynamic> and Func<object>.

class Program
{
    public static void CandidateMethod(Func<object> fun)
    {
        Console.WriteLine("Method that has a Func<object> parameter.");
    }

    public static void CandidateMethod(Func<string> fun)
    {
        Console.WriteLine("Method that has a Func<string> parameter.");
    }

    static void Main(string[] args)
    {
        dynamic dyn = 15;
        CandidateMethod(() => { return dyn; });
    }
}
// Output in Visual Studio 2012:
// Method that has a Func<object> parameter.

// Output in Visual Studio 2010 if Microsoft.CSharp is referenced:
// Method that has a Func<string> parameter.

// Output in Visual Studio 2010 if Microsoft.CSharp isn't referenced (for instance, in a Unit Test Project):
// Method that has a Func<object> parameter.

See Also

Reference

Lambda Expressions (C# Programming Guide)

params (C# Reference)

dynamic (C# Reference)

Concepts

Named and Optional Arguments (C# Programming Guide)

Other Resources

Getting Started with Visual C#

When is a non-breaking language fix breaking?