Visual C# 2010 重大更改

下表列出了 Visual C# 2010 中的重大更改,这些更改可能会使以前使用 Visual C# 2008 创建的应用程序无法编译,或者可能会更改这类应用程序的运行时行为。

类别

问题

说明

程序集绑定

程序集绑定将两个程序集视为等效的。

如果 C# 2010 应用程序同时引用特定引用程序集的 .NET Framework 版本和 .NET Framework for Silverlight 版本并且还使用 extern alias,则会导致编译器错误。 默认情况下,程序集绑定将两个程序集视为等效的。

若要解决此错误,可使用 /appconfig 编译器选项指定 app.config 文件的位置,并在该文件中使用 <supportPortability> 标记禁用默认行为。 有关更多信息,请参见 /appconfig(C# 编译器选项)

如果您正在使用 Microsoft Build Engine (MSBuild) 生成应用程序,则应将适当的标记添加到 .csproj 文件。

协变和逆变

为泛型接口和委托(如 IEnumerable<T>Func<TResult>)添加了新的隐式转换。

泛型接口和委托(如 IEnumerable<T>Func<TResult>)现在具有了用于泛型类型参数的隐式转换。 例如,在 C# 2010 中,可将 IEnumerable<string> 隐式转换为 IEnumerable<object>,这可能导致在以下情况下产生不同行为。

有关更多信息,请参见协变和逆变(C# 和 Visual Basic)

Null 合并运算符

Null 合并运算符 (??) 不允许使用未赋值的局部变量。

在 C# 2010 中,即使左侧的操作数保证不为 null,也不能使用未赋值的局部变量作为 null 合并运算符右侧的操作数。

例如,以下代码可在 C# 2008 中编译,但会在 C# 2010 中生成编译器错误 CS0165

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

方法组类型推理

编译器可为方法组推理泛型和非泛型委托,这会带来不确定性。

在 C# 2008 中,编译器无法为方法组推断泛型委托。 因此,它始终使用非泛型委托(如果存在)。

在 C# 2010 中,会为方法组推断泛型和非泛型委托,编译器推断两者的可能性相等。 如果同时具有某一委托的泛型和非泛型版本并且两者都满足要求,则可能带来不确定性。 例如,下面的代码可在 C# 2008 中编译并且会调用使用非泛型委托的方法。 在 C# 2010 中,此代码会生成报告调用不明确的编译器错误。

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

可选参数

现在,C# 可识别 OptionalAttribute,这可能导致方法重载解决方法发生更改。

在 C# 2008 中,由于 C# 不支持可选参数,因此编译器会忽略 OptionalAttribute

C# 2010 引入了可选参数。 还可以通过使用新语言语法或使用 OptionalAttribute 来声明可选参数。 如果在 C# 2008 中将 OptionalAttribute 用于与其他支持可选参数的语言(例如,Visual Basic)进行互操作,C# 2008 将始终只选择在方法调用中列出所有参数的方法。 C# 2010 可能会选取具有可选参数的方法,即使方法调用中未指定这些参数。

下面的代码在 C# 2008 会从基类调用方法,因为忽略了可选特性,并且编译器会假定派生类中的方法始终需要字符串参数。 在 C# 2010 中,代码从派生类中调用方法,因为此方法签名现在与方法调用匹配。

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

有关更多信息,请参见命名实参和可选实参(C# 编程指南)

嵌入的互操作类型

如果尝试使用 CoClass 创建嵌入的 COM 类型的实例,则会导致编译器错误。

在 C# 2010 中添加对互操作程序集(如 Microsoft.Office.Interop.WordMicrosoft.Office.Interop.Excel)的引用时,将嵌入此程序集中的类型。 有关更多信息,请参见演练:嵌入托管程序集中的类型(C# 和 Visual Basic)/link(C# 编译器选项)

当在代码中创建嵌入 COM 类型的实例时,必须使用适当的接口创建该实例。 如果尝试使用 CoClass 创建嵌入 COM 类型的实例,则编译器会报告错误。

// 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();

嵌入的互操作类型

通过 get_ 和 set_ 方法无法访问索引属性。

嵌入 COM 类型时,将动态调度对 COM 对象的所有调用。 如下面的代码示例所示,如果尝试通过 get_Range 方法访问索引属性 Range,则 C# 运行时绑定器将在类中查找用户定义的 get_Range 方法,而此方法并不存在。 若要避免此问题,请对索引属性使用 C# 2010 语法。 有关更多信息,请参见如何:通过使用 Visual C# 2010 功能访问 Office 互操作对象(C# 编程指南)

// 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]];

事件同步

现在,使用 CompareExchange 方法可实现在编译器生成的添加和删除方法中,同步向事件的支持字段的写入。 这可能会导致争用条件。

在 C# 2010 中,通过使用 CompareExchange 方法代替 MethodImplAttribute,可同步对编译器生成的添加和删除方法的支持字段的更改。

这可能导致 C# 2008 中不存在的争用条件,如下面的代码示例中所示。

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() { }
}

若要避免争用条件,请修改 Loop 方法,如下面的代码示例所示。

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

请参见

其他资源

Visual C# 入门

MSBuild