Visual C# 2010 Breaking Changes

The following table lists breaking changes in Visual C# 2010 that might prevent an application that was created in Visual C# 2008 from compiling, or that might change its run-time behavior.

Category

Issue

Description

Assembly binding

Assembly binding treats two assemblies as equivalent.

C# 2010 applications that reference both the .NET Framework version and the .NET Framework for Silverlight version of a particular reference assembly at the same time, and that also use extern alias, cause compiler errors. By default, the assembly binding treats the two assemblies as equivalent.

To resolve the error, use the /appconfig compiler option to specify the location of an app.config file that disables the default behavior by using a <supportPortability> tag. For more information, see /appconfig (C# Compiler Options).

If you are building an application by using Microsoft Build Engine (MSBuild), add the appropriate tag to the .csproj file.

Covariance and contravariance

A new implicit conversion is added for generic interfaces and delegates such as IEnumerable<T> and Func<TResult>.

Generic interfaces and delegates such as IEnumerable<T> and Func<TResult> now have an implicit conversion for generic type arguments. For example, in C# 2010, IEnumerable<string> can be implicitly converted to IEnumerable<object>, which might cause different behavior in the following scenarios.

For more information, see Covariance and Contravariance (C# and Visual Basic).

Null-coalescing operator

The null-coalescing operator (??) does not allow unassigned local variables.

In C# 2010, you cannot use unassigned local variables as operands on the right side of the null-coalescing operator, even if the operand on the left side is guaranteed not to be null.

For example, the following code compiles and C# 2008 but produces Compiler Error CS0165 in C# 2010.

int? i;
int? j;
int? x = (i = 2) ?? j;

Method group type inference

The compiler can infer both generic and non-generic delegates for method groups, which might introduce ambiguity.

In C# 2008, the compiler cannot infer generic delegates for method groups. Therefore, it always uses a non-generic delegate if one exists.

In C# 2010, both generic and non-generic delegates are inferred for method groups, and the compiler is equally likely to infer either. This can introduce ambiguity if you have generic and non-generic versions of a delegate and both satisfy the requirements. For example, the following code complies in C# 2008 and calls a method that uses a non-generic delegate. In C# 2010, this code produces a compiler error that reports an ambiguous call.

public class Sample
{
    delegate string NonGenericDelegate();
    delegate T GenericDelegate<T>();
    string UseDelegate(NonGenericDelegate del)
    {
        return null;
    }

    T UseDelegate<T>(GenericDelegate<T> del)
    {
       return default(T);
    }

    public string Test()
    {
       // This line produces 
       // a compiler error in C# 2010.
       return UseDelegate(Test);
    }
}

Optional parameters

C# now recognizes OptionalAttribute, which might cause changes in method overload resolution.

In C# 2008, the compiler ignores OptionalAttribute because C# does not support optional parameters.

C# 2010 introduces optional parameters. You can declare optional parameters by using the new language syntax or by using OptionalAttribute. If you use OptionalAttribute in C# 2008 for interoperability with other languages that support optional parameters (for example, Visual Basic), C# 2008 always chooses only methods that have all the parameters listed in the method call. C# 2010 might pick a method that has optional parameters, even if these parameters are not specified in the method call.

The following code calls a method from a base class in C# 2008 because an optional attribute is ignored and the compiler behaves as though the method in the derived class always requires a string parameter. In C# 2010, the code calls a method from a derived class because this method signature now matches the method call.

class Program
{
    public static void Main(string[] args)
    {
        var obj = new Derived();
        obj.Method();
    }
}

class Base
{
    public void Method() 
    { 
        Console.WriteLine(
            "Base class + no optional parameters"); 
    }
}

class Derived : Base
{
    public void Method(
        [Optional][DefaultParameterValue("Hello")] 
        string s) 
    { 
        Console.WriteLine(
            "Derived class + an optional parameter");
    }
}
// Prints different results.
// C# 2008: Base class + no optional parameters
// C# 2010: Derived class + an optional parameter

For more information, see Named and Optional Arguments (C# Programming Guide).

Embedded interop types

Attempting to create an instance of an embedded COM type by using CoClass causes a compiler error.

In C# 2010, when you add a reference to an interop assembly such as Microsoft.Office.Interop.Word or Microsoft.Office.Interop.Excel, the types from this assembly are embedded. For more information, see Walkthrough: Embedding Types from Managed Assemblies (C# and Visual Basic) and /link (C# Compiler Options).

When you create an instance of an embedded COM type in your code, you must create the instance by using the appropriate interface. If you try to create an instance of an embedded COM type by using the CoClass, the compiler reports an error.

// Add the following statement
// at the beginning of the file:
// using Word = Microsoft.Office.Interop.Word;
// This statement does not compile in C# 2010.
Word.Application wordClass = 
    new Word.ApplicationClass();
// Use the following code instead.
Word.Application wordInterface = 
    new Word.Application();

Embedded interop types

Indexed properties cannot be accessed by get_ and set_ methods.

When you embed COM types, all calls to COM objects are dispatched dynamically. As shown in the following code example, if you try to access an indexed property Range by using the get_Range method, the C# run-time binder looks for the user-defined get_Range method in the class, and this method does not exist. To avoid this problem, use the C# 2010 syntax for indexed properties. For more information, see How to: Access Office Interop Objects by Using Visual C# 2010 Features (C# Programming Guide).

// Add the following statement
// at the beginning of the file:
// using Excel = Microsoft.Office.Interop.Excel;
Excel.Application excelApp = new Excel.Application();
excelApp.Visible = true;
excelApp.Workbooks.Add(
    Excel.XlWBATemplate.xlWBATWorksheet);
Excel.Worksheet sheet = 
    excelApp.ActiveSheet as Excel.Worksheet;
// The following statement throws 
// a run-time excpetion in C# 2010.
Excel.Range rangeOld = 
    sheet.get_Range(
        sheet.Cells[1, 1], sheet.Cells[2, 2]);
// Use the following syntax instead.
Excel.Range rangeNew = 
    sheet.Range[sheet.Cells[1, 1], 
                sheet.Cells[2, 2]];

Event synchronization

Synchronization for writing to an event's backing field in the compiler-generated add and remove methods is now achieved by using the CompareExchange method. This might cause a race condition.

In C# 2010, synchronization for changing the backing field for the compiler-generated add and remove methods is achieved by using the CompareExchange method instead of MethodImplAttribute.

This might cause race conditions that were not present in C# 2008, as shown in the following code example.

using System;
using System.Threading;

class Sample
{
    public event Action sampleEvent;

    static void Main()
    {
        new Sample().Loop();
    }

    void Loop()
    {
        new Thread(() => Test.Method(this)).Start();
        while (true)
        {
            lock (this)
            {
                if (sampleEvent != null)
                {
                    // In C# 2010, sampleEvent 
                    // can be null here,
                    // which causes 
                    // a run-time exception.
                    sampleEvent();
                }
            }
        }
    }
}

class Test
{
    public static void Method(Sample arg)
    {
        while (true)
        {
            arg.sampleEvent += Method;
            arg.sampleEvent -= Method;
        }
    }
    static void Method() { }
}

To avoid the race condition, modify the Loop method as shown in the following code example.

void Loop()
{
   new Thread(() => Test.Method(this)).Start();
   while (true)
   {
       lock (this)
       {
           // Create a local copy of the delegate.
           Action local = sampleEvent;
           if (local != null)
           {
               local();
           }
        }
    }
}

See Also

Other Resources

Getting Started with Visual C#

MSBuild