Visual C# 2008 重大更改

更新: 2008 年 7 月

Visual C# 2008 Service Pack 1 中的重大更改

下表列出了 Visual C# 2008 Service Pack 1 中的所有重大更改,这些更改可能影响在 Visual C# 2008 原始发行版本或在 Visual C# 2005 中创建的应用程序。

更改编号

类别

问题

说明

1

重载决策

现在在方法重载决策中包含了对指针类型数组的类型推理。

在 Visual C# 2008 及更低版本中,类型推理会导致从方法重载决策过程中排除指针类型的数组。在下面的代码中,Visual C# 2005 编译器会选择 Test 的非泛型版本,原因是 Test 的泛型版本由于具有类型参数 int*[] 而不予考虑。而在 Visual C# 2008 中会选择 Test 的泛型版本。

using System.Collections.Generic;
unsafe class Program
{
    static void Main()
    {
        IEnumerable<int*[]> y = null;
        Test(y); 
    }
// Selected by Visual C# 2008.
    static void Test<S>(IEnumerable<S> x) { } // Selected by Visual C# 2005.
    static void Test(object o) { } 
}

2

索引器

除方法之外,编译器现在对索引器和属性也生成错误 CS0466。

在 Visual C# 2008 的原始发行版及更低版本中,可以定义索引器的显式实现,其实现具有 params 参数而接口定义没有该参数。此构造违反了规范。在 Visual C# 2008 SP1 中,此构造生成编译器错误 CS0466,如下面的代码所示。

interface I
{
    int this[int[] p] { set; }
}
class Base : I
{
// Produces CS0466:
    int I.this[params int[] p]    {
        set
        {
        }
    }

}

3

可以为 null 的类型和 ?? 表达式

编译器现在能够正确计算将可以为 null 的变量与自身进行比较的表达式。

在 Visual C# 2008 的原始发行版中,下面的代码可以编译并在运行时输出“false”。在 Visual C# 2008 Service Pack 1 中将产生编译器警告(等级 3)CS1718 并输出“true”。

static class Program
{
    static void Main()
    {
        int? x = null;
        bool y = x == x;
        Console.WriteLine(y);
    }
}

4

迭代器中的 try-finally

更改了迭代器中包含 break 语句的嵌套 finally 块的执行方式。

在 Visual C# 2008 的原始发行版中,下面的代码两次执行外部 finally。在 Visual C# 2008 SP1 中,外部 finally 执行一次。

using System;
using System.Collections;
using System.Collections.Generic;
public class Test
{
    public static void Main()
    {
        Console.WriteLine("in main");
        foreach (int i in GetInts())
        {
            Console.WriteLine("in foreach");
            break; 
        }
    }
    static IEnumerable<int> GetInts()
    {
        Console.WriteLine("in GetInts");
        while (true)
        {
            Console.WriteLine("in while");
            try
            {
                Console.WriteLine("in outer try");
                try
                {
                    Console.WriteLine("in inner try before yield");
                    yield return 1;
                    Console.WriteLine("in inner try after yield");
                    break;
                }
                finally
                {
                    Console.WriteLine("in inner finally");
                }
            }
            finally
            {
                Console.WriteLine("in outer finally");
            }
        }
    }
}

5

表达式目录树

不再发生对表达式目录树中的方法表达式的错误装箱。

在 Visual C# 2008 的原始发行版中,下面的代码输出 7、0。由于错误地对 S 进行装箱,Console.WriteLine(e.Compile()(default(T))); 代码行输出 0。在 Visual C# 2008 SP1 中,不发生装箱,该程序输出 7、7。

using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
    static void Main()
    {
        Test<S>();
    }
    static void Test<T>() where T : I
    {       
        Expression<Func<T, int>> e = x => x.SetX() + x.X;
// No boxing in SP1:
        Console.WriteLine(e.Compile()(default(T))); 
    }
}
interface I
{
    int X { get; }
    int SetX();
}
struct S : I
{
    public int X { get; private set; }
    public int SetX()
    {
        X = 7;
        return 0;
    }
}

6

对象初始值设定项

更正了对象初始值设定项中值类型的初始化。

在 Visual C# 2008 的原始发行版中,下面示例中的局部变量 b 初始化不正确,其成员 X 的值为零。在 Visual C# 2008 SP1 中,S.X 在两个 new 表达式中都正确初始化为 1。

using System;
using System.Linq;
using System.Linq.Expressions;
    class Program
    {
        static void Main()
        {
            Test<S>();
        }
        static void Test<T>() where T : I, new()
        {
            var a = new T();
            a.X = 1;
            Console.WriteLine(a.X);
            var b = new T { X = 1 };
            Console.WriteLine(b.X);
        }
    }
    interface I
    {
        int X { get; set; }
    }
    struct S : I
    {
        public int X { get; set; }
    }
// Original release version of Visual C# 2008 output: 1 0
// Visual C# 2008 SP1 output: 1 1

7

类型转换

null 文本不再可以转换为枚举值。

在 Visual C# 2008 的原始发行版中,null 文本在某些情况下可以转换为枚举值。在 Visual C# 2008 SP1 中,如果尝试这样做(如下面的示例所示),将产生编译器错误 CS1502编译器错误 CS1503

enum MyEnum
{
    Zero = 0,
    One = 1
}
class MyClass { }
class Program
{
    static void Main(string[] args)
    {
// Produces CS1502 and CS1503:
        Test((MyClass)null);         }
    static void Test(MyEnum x)
    {
        System.Console.WriteLine(x);
    }
}

8

表达式目录树

无效的表达式目录树现在引发正确的异常。

在 Visual C# 2008 的原始发行版中,如果表达式目录树调用在指定类型上不存在的方法,该表达式目录树将引发 System.Security.VerificationException。在 Visual C# 2008 SP1 中将引发 System.ArgumentException,如下面的代码所示。

using System;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
class Program
{
    public struct S { }
    static void Main()
    {
        Type t = typeof(System.Enum);
        MethodInfo m = t.GetMethod("GetTypeCode");
        ParameterExpression p = Expression.Parameter(typeof(S), "s");
        Expression<Func<S, TypeCode>> e = Expression.Lambda<Func<S, TypeCode>>(
// Throws System.ArgumentException in Visual C# 2008 SP1:
            Expression.Call(p, m), p); 
        Func<S, TypeCode> f = e.Compile();
// Throws System.Security.VerificationException in the
// original release version of Visual C# 2008: 
        Console.WriteLine(f(new S())); 
    }
}

9

属性

CharSet.Unicode 现在传播到 C# 为固定数组字段生成的帮助器类型。

C# 编译器生成帮助器类型以封装固定数组。在 Visual C# 2008 的原始发行版及更低版本中,数组的布局始终为 ANSI,即使 StructLayout 属性指定 CharSet.Unicode 也如此。在 C# 源代码中无法更改这一点。在 Visual C# 2008 SP1 中,将严格使用 StructLayout 属性中指定的 CharSet 值来构造帮助器类,如下面的代码所示。

using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
unsafe struct Test
{
    public fixed char Chars[8];
}
class Program
{
    static void Main(string[] args)
    {
    }
}
Original release version of Visual C# 2008 MSIL:
.class sequential ansi sealed nested public beforefieldinit '<Chars>e__FixedBuffer0'
       extends [mscorlib]System.ValueType
{
  // ... 
} // end of class '<Chars>e__FixedBuffer0'
Visual C# 2008 SP1 MSIL:
.class sequential unicode sealed nested public beforefieldinit '<Chars>e__FixedBuffer0'
       extends [mscorlib]System.ValueType
{
  // . . . 
} // end of class '<Chars>e__FixedBuffer0'

10

溢出检查

stackalloc 现在执行溢出检查。

在 Visual C# 2008 的原始发行版中,stackalloc 分配可能在不导致异常的情况下失败。这是因为在将数组长度与每个元素的大小相乘时,没有检查所生成的 Microsoft 中间语言 (MSIL) 中的 mul 指令。在 Visual C# 2008 SP1 中,生成的是 mul.ovf 指令而不是 mul 指令,因此若在运行时尝试分配操作,在发生溢出时将生成 System.OverflowException。

class Program
{
    static void Main(string[] args)
    {
        int var = 0x40000000;
        unsafe
        {
            // 0x40000000 * sizeof(int) does not fit in an int.
            int* listS = stackalloc int[var]; 
// Visual C# 2008 SP1: System.OverflowException.
            listS[0] = 5; 
// Original release version of Visual C# 2008: 
// System.NullReferenceException.
        }
    }
}

11

标准查询运算符

对非泛型集合执行的查询现在使用标准 C# 强制转换语义。

在针对非泛型集合如 System.Collections.ArrayList 的 LINQ 查询表达式中,编译器会重写查询的 from 子句以包含对 Cast<T> 运算符的调用。Cast<T> 将所有元素类型转换为在查询的 from 子句中指定的类型。而且,在 Visual C# 2008 的原始发行版中,Cast<T> 运算符还执行一些值类型转换和用户定义的转换。但是,这些转换是通过使用 System.Convert 类而不是标准 C# 语义执行的。这些转换在某些情况下还会导致严重的性能问题。Visual C# 2008 SP1 中对 Cast<T> 运算符进行了修改,对于数值类型转换和用户定义的转换将引发 InvalidCastException。此更改消除了非标准 C# 强制转换语义和性能问题。下面的示例对此更改进行了阐释。

using System;
using System.Linq;
class Program
{
    public struct S { }
    static void Main()
    {
        var floats = new float[] { 2.7f, 3.1f, 4.5f };
        var ints = from int i in floats 
                   select i;
// Visual C# 2008 SP1 throws InvalidCastException. 
        foreach (var v in ints) 
            Console.Write("{0} ", v.ToString());
        // The original release version of Visual C# 2008
        // compiles and outputs 3 3 4
    }
}

Visual C# 2008 原始发行版中的重大更改

下表列出了 Visual C# 2008 原始发行版中的所有重大更改,这些更改可能阻止在 Visual C# 2005 中创建的应用程序的编译,或可能更改该应用程序的运行时行为。

更改编号

类别

问题

说明

12

类型转换

现在允许将任何具有零值的常量表达式转换为枚举。

文本 0 可隐式转换为任何枚举类型。在 Visual C# 2005 及更低版本的编译器中,也有一些计算结果为 0 的常量表达式可以隐式转换为任何枚举类型,但决定可以转换哪些表达式的规则不明确。在 Visual C# 2008 中,所有等于 0 的常量表达式都可以隐式转换为任何枚举类型。

这可能导致现有代码的行为发生某些变化,例如依赖不存在此类隐式转换的方法重载决策。下面的代码在 Visual C# 2005 及更低版本的编译器中将成功编译,编译时仅将 short 值上的方法调用解析为 int 重载。此调用在 Visual C# 2008 中是不明确的,因为 short 值也可以隐式转换为 E。在 Visual C# 2008 中,此代码的行为更改为允许转换计算结果为零的任何常量表达式。

public enum E
{
    Zero = 0,
    One = 1,
} 
class A
{
    public A(string s, object o)
    { System.Console.WriteLine("{0} => A(object)", s); } 
    public A(string s, E e)
    { System.Console.WriteLine("{0} => A(Enum E)", s); }
} 
class B
{
    static void Main()
    {
        A a1 = new A("0", 0);
        A a2 = new A("1", 1);
        A a3 = new A("(int) E.Zero", (int) E.Zero);
        A a4 = new A("(int) E.One", (int) E.One);
    }
}
Visual C# 2005 output:
0 => A(Enum E)
1 => A(object)
(int) E.Zero => A(object)
(int) E.One => A(object)
Visual C# 2008 output:
0 => A(Enum E)
1 => A(object)
(int) E.Zero => A(Enum E)
(int) E.One => A(object)

13

属性

现在,当一个程序集中两次出现同一 TypeForwardedTo 属性时将发生错误。

在 Visual C# 2005 中,一个程序集包含以同一类型为目标的两个 System.Runtime.CompilerServices.TypeForwardedTo 属性时不产生错误。在 Visual C# 2008 中,这将产生编译器错误 CS0739,如下面的示例所示。

// Class1.cs
// Causes CS0739:
    [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Test))]
    [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Test))] 
    public class Test
    {
        public static int Main()
        {
            Test f = new Test();
            return f.getValue();
        }
    }
    // Library1.cs
    public class Test
    {
        public int getValue()
        {
            return 0;
        }

}

14

类型错误

添加了关于在结构中使用引用类型成员的新警告。

结构的明确赋值规则要求:要么将结构设置为该结构的类型的现有实例,要么在引用结构的每个成员之前对该成员进行赋值。在 Visual C# 2005 中,当使用结构的未赋值引用类型成员时不产生警告或错误。在 Visual C# 2008 中,这将产生编译器警告(等级 1)CS1060,如下面的示例所示。

    public class U { public int i;}
    public struct T { public U u;}
    class Program
    {
        static void Main()
        {
            T t;
// Produces CS1060:    
            t.u.i = 0; 
        }
    }

15

溢出检查

更正了对 const 十进制类型的范围检查。

在 Visual C# 2005 中,当强制转换 const 十进制类型时,范围检查不总是正确的,可能导致不正确的编译器错误。在 Visual C# 2008 中,下面的代码产生正确的错误:编译器错误 CS0031

        static void Main()
        {
            const decimal d = -10m;
            unchecked
            {
                const byte b = (byte)d; //CS0031
            }
        }

16

溢出检查

对 long 的越界转换现在产生正确的编译器错误。

在 Visual C# 2005 中,下面的代码不产生编译器错误。在 Visual C# 2008 中,此代码产生编译器错误 CS0031

class Conversion 
    {
        static void Main() 
        {
            long l2 = (long) 9223372036854775808M; //CS0031 
        }
    }

17

固定大小的缓冲区

现在,如果在为不安全结构中固定大小的缓冲区赋值之前访问该缓冲区,将会产生编译器错误。

不安全指针的明确赋值规则要求在设置指针之后才能取消对指针的引用。在 Visual C# 2005 中,当不安全结构包含指向数组的指针时,在为指针赋值前访问该指针不产生编译器错误。在 Visual C# 2008 中,这将产生编译器错误 CS0165,如下面的代码所示。

    unsafe class Test
    {
        static void Main()
        {
            S* ps;
            ps->i[0]++;        } // CS0165
    }
    unsafe struct S
    {
        public fixed int i[10];
    }

18

null 合并表达式中现在保留副作用。

明确赋值和 ?? 运算符。

在 Visual C# 2005 中,在某些情况下不保留 null 合并表达式左侧的副作用。在这类情况下,下面示例中的第二个 Console.WriteLine 语句产生不正确的编译器错误,指示 b 未赋值。在 Visual C# 2008 中,相同的代码将正确编译而不产生错误。

        static void Main()
        {
            int? a, b;
            a = null;
            Console.WriteLine((b = null) ?? 17);
// No error in Visual C# 2008:Console.WriteLine(a + b);  

}

19

迭代器中的 try-finally

现在,当 try 块中的迭代器通过 continue 或 goto 退出时,将执行 finally 块。

在 Visual C# 2005 的 try-finally 构造中,当使用 goto 或 continue 语句将控制权传递到 try 块中的迭代器块以外时,不会执行 finally 块。在 Visual C# 2008 中,这些情况下将执行 finally 块。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DisposeTest
{
    class A : IDisposable
    {
        int m_n;
        internal A(int n)
        {
            m_n = n;
        }
        internal void Nothing() { }
        ~A()
        {
            Console.WriteLine("failed to dispose {0}", m_n);
        }
        #region IDisposable Members
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Console.WriteLine("dispose {0}", m_n);
        }
        #endregion
    }
    class Program
    {
        static IEnumerable<A> B()
        {
            for (int nCount = 0; nCount < 2; nCount++)
            {
                Console.WriteLine("loop start");
                using (A A = new A(nCount))
                {
                    Console.WriteLine("using start");
                    // Section 1.
                    // Dispose not called correctly in Visual C# 2005.
                    if ((nCount % 2) == 0)
                        continue;
                    // Section 2.
                    // Dispose not called correctly in Visual C# 2005.
                    yield return A;
                    Console.WriteLine("using end");
                }
                Console.WriteLine("loop end");
            }
            yield break;
        }
        static void Main(string[] args)
        {
            foreach (A A in B())
            {
                A.Nothing();
            }
            Console.ReadLine();
        }
    }

}

20

基类和接口

类构造现在忽略基类中相同接口成员的显式实现。

在 Visual C# 2005 中,当类不提供接口成员的实现时,编译器将代之以基类实现,即使基类实现声明为显式接口实现也如此。此行为不符合欧洲计算机制造商协会 (ECMA) 规范。Visual C# 2008 正确实现了该规范。在下面的示例中,Visual C# 2005 将输出“B.Test”。Visual C# 2008 将正确输出“A.Test”并忽略类 B 中的 Test 方法,因为该方法是显式接口实现。

using System;
interface ITest
{
    string Test { get; }
    string Test2 { get; }
}
class A : ITest
{
    public string Test { get { return "A.Test"; } }
    public string Test2 { get { return "A.Test2"; } }
}
class B : A, ITest
{
    string ITest.Test { get { return "B.Test"; } }
    string ITest.Test2 { get { return "B.Test2"; } }
}
class C : B, ITest
{
    string ITest.Test2 { get { return "C.Test2"; } }
}
class Program
{
    static void Main()
    {
        C c = new C();
        Console.WriteLine(c.Test); 
// Visual C# 2008: "A.Test"
    }

}

21

属性

使用已过时的成员现在生成编译器警告。

可以使用 Obsolete 属性标记方法,调用这些方法会在编译时导致错误或警告。对虚方法设置将此属性时,必须将此属性设置在基方法上。如果在重写方法上设置 Obsolete 属性,则在调用时不会导致编译器错误或警告。在 Visual C# 2005 中,编译器允许在重写方法上设置 Obsolete 属性,但该属性放在那里不起任何作用。在 Visual C# 2008 中,将产生编译器警告编译器警告(等级 1)CS0809“过时成员‘A.Filename’重写未过时成员‘Error.Filename’”。下面的示例导致此警告。

class A : Error
{
    [System.ObsoleteAttribute("Obsolete", true)]
    public override string Filename
    {
        set
        {
        }
    }
    public static void Main() { }
}
public class Error
{
    public virtual string Filename
    {
        set
        {
        }
        get
        {
            return "aa";
        }
    }
}
class B
{
    void TT()
    {
        new A().Filename = "Filename";
    }
}

22

生成错误

使用 /pdb 编译器选项时不使用 /debug 现在将产生错误。

在 Visual C# 2005 中,指定 /pdb 选项而不指定 /debug 选项时不显示警告或错误。Visual C# 直接创建发行版本而不生成 .pdb 文件。在 Visual C# 2008 的原始发行版中,如果指定 /pdb 而不指定 /debug,编译器将显示编译器错误 CS2036

23

类型错误

现在,当 switch 条件为 void 时,将产生错误。

在 Visual C# 2005 中,当在 switch 语句中使用 void 方法调用时,不会产生错误。在 Visual C# 2008,这将产生编译器错误 CS0151

class C
{
    static void Main()
    {
// Produces CS0151:
        switch (M()) 
        {
            default:
                break;
        }
    }
    static void M()
    {
    }

}

24

溢出检查

从十进制常量到整数常量的转换现在产生与以前不同的编译器错误。

在 Visual C# 2005 中,下面的代码将产生编译器错误 CS0133“指派给‘b’的表达式必须是常量”。

const byte b = unchecked((byte)256M);

在 Visual C# 2008 中,此代码产生编译器错误 CS0031:“常量值‘256M’无法转换为‘byte’”。注意,即使应用了 unchecked 修饰符,仍会产生该错误。

25

常量表达式

常量表达式更密切符合规范。

在 Visual C# 2008 中,更正了 Visual C# 2005 错误地允许在常量表达式中使用运算符和变量的若干问题。在 Visual C# 2005 中,下面的代码不产生编译错误。在 Visual C# 2008 中,此代码将产生编译器错误 CS0165编译器警告(等级 1)CS0184编译器警告(等级 3)CS1718

class Program
{
    public static int Main()
    {
        int i1, i2, i3, i4, i5;
        // 'as' is not permitted in a constant expression.
        if (null as object == null)
            i1 = 1;
        // 'is' is not permitted in a constant expression.
        if (!(null is object))
            i2 = 1;
        // A variable is not permitted in a constant expression.
        int j3 = 0;
        if ((0 == j3 * 0) && (0 == 0 * j3))
            i3 = 1;
        int j4 = 0;
        if ((0 == (j4 & 0)) && (0 == (0 & j4)))
            i4 = 1;
        int? j5 = 1;
// Warning CS1718: Comparison made to same variable:
        if (j5 == j5) 
 
            i5 = 1;
        System.Console.WriteLine("{0}{1}{2}{3}{4}{5}", i1, i2, i3, i4, i5);
        return 1;
    }
}

26

类型错误

现在,在委托或 lambda 表达式中使用静态类型作为参数将产生错误。

在 Visual C# 2005 中,将静态类型用作委托或匿名方法的参数不会产生错误。静态类型不能实例化,因而不能用作方法参数的类型。Visual C# 2005 版编译器允许在委托和匿名方法声明中使用静态类型作为参数类型。如果传递 null 作为参数,可调用此类委托。在 Visual C# 2008 中,如果使用静态类型作为委托或匿名方法的参数,将产生编译器错误 CS0721,如下面的示例所示。

public static class Test { }
public class Gen<T> { }
// Produces CS0721:
delegate int D(Test f); 
public class TestB
{
    public static void Main()
    {
        D d = delegate(Test f) { return 1; };
    }

}

27

可以为 null 的类型和 ?? 表达式

可将常量强制转换为可为 null 的类型,再将该常量赋给可为 null 的类型(更广泛的类型),这样做不会产生警告。

在 Visual C# 2005 中,下面的代码将产生编译器警告(等级 3)CS0219。在 Visual C# 2008 中,此代码不产生警告。

ushort? usq2 = (byte?)0;

28

重载决策

当匿名方法上发生不明确的重载决策时现在将产生错误。

编译器必须解析对重载方法的方法调用以确定调用哪个特定的重载。当对调用的参数类型做部分推理时,要调用哪个特定的重载将变得不明确。这将导致编译器错误。

在将匿名方法作为委托参数进行传递的情况下,将对该匿名方法的委托类型做部分推理。在编译器选择正确的重载时,这可能导致出现多义性。

在 Visual C# 2005 中,当没有匿名方法的唯一最佳重载时,编译器不总是产生错误。在 Visual C# 2008 中,这将产生编译器错误 CS0121,如下面的示例所示。

class Program
{
    static int ol_invoked = 0;
    delegate int D1(int x);
    delegate T D1<T>(T x);
    delegate T D1<T, U>(U u);
    static void F(D1 d1) { ol_invoked = 1; }
    static void F<T>(D1<T> d1t) { ol_invoked = 2; }
    static void F<T, U>(D1<T, U> d1t) { ol_invoked = 3; }
    static int Test001()
    {
// Produces CS0121:
        F(delegate(int x) { return 1; });         if (ol_invoked == 1)
            return 0;
        else
            return 1;
    }
    static int Main()
    {
        return Test001();
    }
}

29

类型错误

现在,如果声明指向托管类型的指针数组,将产生错误。

不允许使用指向引用类型的不安全指针,这些指针将导致编译器错误。在 Visual C# 2005 中,可以声明指向托管类型的指针数组。在 Visual C# 2008 中,这将产生编译器错误 CS0208:“无法获取托管类型(‘T’)的地址和大小,或无法声明指向它的指针。”

unsafe class TestClass<T>
{
// Produces CS0208:
    static T*[] x = { }; 
// Produces CS0208:
    static void Test(T*[] arr) 
    {
    }
// Produces CS0208:
    static T*[] TestB() 
    {
        return x;
    }

}

30

重载决策

当重载决策候选方法仅因 ref 或 out 而不同时,现在将产生警告。

在 Visual C# 2005 中,当 C# 编译器对泛型类型执行重载决策时,不验证类型参数是否导致候选方法仅因 ref 或 out 而不同。因此,将由公共语言运行时 (CLR) 在运行时选择方法,而它只是选择列表中的第一个方法。在 Visual C# 2008 中,当编译器检测到重载决策的两个候选方法仅因 ref 或 out 而不同时,将产生编译器警告(等级 1)CS1956。下面的示例对此情况进行了阐释。

using System;
class Base<T, S>
{
// Produces CS1956:
    public virtual void Test(out T x) 
    {
        Console.WriteLine("Test(out T x)");
        x = default(T);
    }
    public virtual void Test(ref S x)
    {
        Console.WriteLine("Test(ref T x)");
    }
}
interface IFace
{
    void Test(out int x);
}
class Derived : Base<int, int>, IFace
{
    static void Main()
    {
        IFace x = new Derived();
        int y;
        x.Test(out y);
    }

}

31

可以为 null 的类型和 ?? 表达式

左侧为 null 的 null 合并表达式不再计算为 null 常量。

在 Visual C# 2005 中,左侧为 null 的 null 合并表达式计算为 null 常量。在 Visual C# 2008 中不再如此。在某些情况下,Visual C# 2005 的行为使得变量被错误地处理为已明确赋值。下面的代码在 Visual C# 2005 中可以编译并运行而不产生错误,但在 C# 2008 中将产生编译器错误 CS0165:“使用了未赋值的局部变量‘x’”。

static void Main()
    {
        int x;
        if (null == (decimal?)(null ?? null)) x = 1;
        // Producers CS0165 in Visual C# 2008:
        System.Console.WriteLine(x);    
    }

请参见

其他资源

Visual C# 入门

修订记录

日期

修订记录

原因

2008 年 7 月

新增主题。

SP1 功能更改。