Export (0) Print
Expand All

Visual C# 2008 Breaking Changes

Visual Studio 2008

Updated: July 2008

The following table lists all the breaking changes in Visual C# 2008 Service Pack 1 that might impact an application that was created in the original release version of Visual C# 2008 or in Visual C# 2005.

Change number

Category

Issue

Description

1

Overload resolution

Type inference is now included on arrays of pointer types in method overload resolution.

In Visual C# 2008 and earlier, type inference causes arrays of pointer types to be excluded from the method overload resolution process. In the following code, the Visual C# 2005 compiler selects the non-generic version of Test because the generic version of Test is excluded from consideration because of its type parameter of int*[]. In Visual C# 2008, the generic version of Test is selected.

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

Indexers

Compiler now produces error CS0466 for indexers and properties in addition to methods.

In the original release version of Visual C# 2008 and earlier versions, it is possible to define an explicit implementation of an indexer in which the implementation has a params parameter but the interface definition does not. This construction is contrary to the specification. In Visual C# 2008 SP1, this construction produces Compiler Error CS0466, as shown in the following code.

interface I
{
    int this[int[] p] { set; }
}

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

}

3

Nullable types and ?? expressions

Compiler now correctly evaluates expressions in which nullable variables are compared to themselves.

In the original release version of Visual C# 2008, the following code compiles and outputs "false" at run time. In Visual C# 2008 Service Pack 1, Compiler Warning (level 3) CS1718 is produced and "true" is output.

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

4

try-finally in iterators

Execution of nested finally blocks from iterators that have break statements is changed.

In the original release version of Visual C# 2008, the following code executes the outer finally two times. In Visual C# 2008 SP1, the outer finally is executed one time.

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

Expression trees

Incorrect boxing of method expressions in expression trees no longer occurs.

In the original release version of Visual C# 2008, the following code outputs 7, 0. The line Console.WriteLine(e.Compile()(default(T))); outputs zero because S is incorrectly being boxed. In Visual C# 2008 SP1, no boxing occurs, and the program outputs 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

Object initializers

Initialization of value types in object initializers has been corrected.

In the original release version of Visual C# 2008, the local variable b in the following example is not initialized correctly, and its member X has a value of zero. In Visual C# 2008 SP1, S.X is correctly initialized to 1 in both new expressions.

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

Type conversions

Null literals are no longer convertible to enum values.

In the original release version of Visual C# 2008, null literals are in some cases allowed to be converted to enum values. In Visual C# 2008 SP1, Compiler Error CS1502 and Compiler Error CS1503 are produced if you try to do this, as shown in the following example.

enum MyEnum
{
    Zero = 0,
    One = 1
}

class MyClass { }

class Program
{
    static void Main(string[] args)
    {
        // Produces CS1502 and CS1503 in Visual C# 2008
        // and Visual C# 2008 SP1:
        Test(null);
        // Produces CS1502 and CS1503 in Visual C# 2008 
        // SP1 but not in Visual C# 2008:
        Test((MyClass)null);         }

    static void Test(MyEnum x)
    {
        System.Console.WriteLine(x);
    }
}

8

Expression trees

Invalid expression tree now throws the correct exception.

In the original release version of Visual C# 2008, an expression tree that contains a method call to a method that is not on the specified type throws a System.Security.VerificationException. In Visual C# 2008 SP1, a System.ArgumentException is thrown, as shown in the following code.

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

Attributes

CharSet.Unicode is now propagated to helper types that C# generates for fixed array fields.

The C# compiler generates helper types to encapsulate fixed arrays. In the original release version of Visual C# 2008 and earlier, the layout of the array is always ANSI, even if the StructLayout attribute specifies CharSet.Unicode. There was no way to change that in C# source code. In Visual C# 2008 SP1, whatever CharSet value is specified in the StructLayout attribute is used to construct the helper class, as shown in the following code.

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

Overflow checking

stackalloc now performs an overflow check.

In the original release version of Visual C# 2008, it is possible for a stackalloc allocation to fail without causing an exception. This is because of an unchecked mul instruction in the generated Microsoft intermediate language (MSIL) when the length of the array is multiplied by the size of each element. In Visual C# 2008 SP1, a mul.ovf instruction is generated instead of a mul, so overflows produce a System.OverflowEx ception when the allocation is attempted at run time.

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

Standard query operators

Queries over non-generic collections now use standard C# cast semantics.

In LINQ query expressions over non-generic collections such as System.Collections.ArrayList, the from clause of the query is rewritten by the compiler to include a call to the Cast<T> operator. Cast<T> converts all element types to the type specified in the from clause in the query. In addition, in the original release version of Visual C# 2008, the Cast<T> operator also performs some value type conversions and user-defined conversions. However, these conversions are performed by using the System.Convert class instead of the standard C# semantics. These conversions also cause significant performance issues in certain scenarios. In Visual C# 2008 SP1, the Cast<T> operator is modified to throw an InvalidCastException for numeric value type and user-defined conversions. This change eliminates both the non-standard C# cast semantics and the performance issue. This change is illustrated in the following example.

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

The following table lists all the breaking changes in the original release version of Visual C# 2008 that might prevent an application that was created in Visual C# 2005 from compiling, or that might change its run-time behavior.

Change number

Category

Issue

Description

12

Type conversions

Conversion of any constant expression with a value of zero to enum is now allowed.

A literal 0 is implicitly convertible to any enum type. In Visual C# 2005 and earlier versions of the compiler, there are also some constant expressions that evaluate to 0 that can implicitly convert to any enum type, but the rule that determines which of these expressions are convertible is unclear. In Visual C# 2008, all constant expressions that are equal to 0 can be implicitly converted to any enum type.

This could cause some changes in the behavior of existing code, such as method overload resolution that relies on the absence of this implicit conversion. The following code compiles successfully on Visual C# 2005 and earlier compilers, resolving the method invocation on the short value only to the int overload. In Visual C# 2008, this invocation is ambiguous because the short value is also implicitly convertible to E. In Visual C# 2008, the behavior is changed to allow the conversion of any constant expression that evaluates to zero.

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

Attributes

Error now occurs when the same TypeForwardedTo attribute is present two times in one assembly.

In Visual C# 2005, no error is produced if an assembly contains two System.Runtime.CompilerServices.TypeForwardedTo attributes that target the same type. In Visual C# 2008, Compiler Error CS0739 is produced, as shown in the following example.

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

Type errors

A new warning about the use of a reference type member in a struct has been added.

Definite assignment rules for structs require that either the struct be set to an existing instance of its type or that each of its members be assigned to before it is referenced. In Visual C# 2005, no warning or error is produced when an unassigned reference type member of a struct is used. In Visual C# 2008, Compiler Warning (level 1) CS1060 is produced, as shown in the following example.

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

Overflow checking

Range-checking on const decimal types has been corrected.

In Visual C# 2005, when you cast const decimal types, range checking is not always correct, and incorrect compiler errors can result. In Visual C# 2008, the following code produces the correct error: Compiler Error CS0031.

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

16

Overflow checking

Out-of-bounds conversions to long now produce the correct compiler error.

In Visual C# 2005, the following code does not produce a compiler error. In Visual C# 2008, it produces Compiler Error CS0031.

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

17

Fixed-size buffers

Accessing a fixed-sized buffer in an unsafe struct before assigning a value to the buffer now produces a compiler error.

Definite assignment rules for unsafe pointers require that the pointer be set before dereferencing the pointer. In Visual C# 2005, when an unsafe struct contains a pointer to an array, accessing the pointer before assigning a value to it did not produce a compiler error. In Visual C# 2008, this produces Compiler Error CS0165, as shown in the following code.

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

unsafe struct S
{
    public fixed int i[10];
}

18

Side effects are now preserved in null coalescing expressions.

Definite assignment and the ?? operator.

In Visual C# 2005, in certain scenarios, the side effects on the left-hand side of a null coalescing expression are not preserved. In such cases, the second Console.WriteLine statement in the following example produces an incorrect compiler error stating that b is unassigned. In Visual C# 2008, the same code compiles correctly without an error.

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 in iterators

The finally block is now executed when an iterator in the try block escapes with continue or goto.

In Visual C# 2005, in a try-finally construction, when control passes out of an iterator block in the try block by using a goto or continue statement, the finally block is not executed. In Visual C# 2008, the finally block is executed in these cases.

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

Base classes and interfaces

Class construction now ignores explicit implementations of the same interface members in base classes.

In Visual C# 2005, when a class does not provide an implementation for an interface member, the compiler substitutes base class implementations even if they are declared as explicit interface implementations. This behavior is not in compliance with the European Computer Manufacturers Association (ECMA) specification. Visual C# 2008 correctly implements the specification. In the following example, Visual C# 2005 prints "B.Test". Visual C# 2008 correctly prints "A.Test" and ignores the Test method in class B because it is an explicit interface implementation.

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

Attributes

Use of an obsolete member now generates a compiler warning.

You can mark methods with the Obsolete attribute to cause either errors or warnings at compile time if the methods are invoked. When you put this attribute on virtual methods, the attribute must be put on the base method. If the Obsolete attribute is put on an override method, it will not cause compiler errors or warnings on invocation. In Visual C# 2005, the compiler allowed you to put the Obsolete attribute on an override method, even though the attribute did not have any effect when it was put there. In Visual C# 2008, compiler warning Compiler Warning (level 1) CS0809 is produced, "Obsolete member 'A.Filename' overrides non-obsolete member 'Error.Filename'." The following example causes this warning.

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

Build errors

Use of the /pdb compiler option without /debug now produces an error.

In Visual C# 2005, no warning or error is displayed when you specify the /pdb option but not the /debug option. Visual C# just creates a Release build without generating the .pdb file. In the original release version of Visual C# 2008, if you specify /pdb without also specifying /debug, the compiler displays Compiler Error CS2036.

23

Type errors

An error is now produced when a switch condition is void.

In Visual C# 2005, no error is generated when a void method invocation is used in a switch statement. In Visual C# 2008, Compiler Error CS0151 is produced.

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

    static void M()
    {
    }
}

24

Overflow checking

Constant decimal to integral conversions now produce a different compiler error.

In Visual C# 2005, the following code would produce Compiler Error CS0133: "The expression being assigned to 'b' must be constant."

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

In Visual C# 2008, Compiler Error CS0031 is produced: "Constant value '256M' cannot be converted to a 'byte'." Note that the error is produced even if the unchecked modifier is applied.

25

Constant expressions

Specification is more closely adhered to regarding constant expressions.

In Visual C# 2008, several issues have been corrected in which Visual C# 2005 would incorrectly allow operators and variables in constant expressions. In Visual C# 2005, the following code compiles without errors. In Visual C# 2008, Compiler Error CS0165 , Compiler Warning (level 1) CS0184 , and Compiler Warning (level 3) CS1718 are produced:

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

Type errors

An error is now produced when a static type is used as a parameter in a delegate or in a lambda expression.

In Visual C# 2005, no error is produced if a static type is used as a parameter to a delegate or anonymous method. Static types cannot be used as the types of method parameters because they cannot be instantiated. The Visual C# 2005 version of the compiler allows static types as parameter types within delegates and anonymous method declarations. If you pass null as the parameter, such delegates can be invoked. In Visual C# 2008, error Compiler Error CS0721 is produced if a static type is used as a parameter to a delegate or anonymous method, as shown in the following example.

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

Nullable types and ?? expressions

No warning is produced when you cast a constant to a nullable type before assigning it to a nullable (of a wider type).

In Visual C# 2005, the following code would produce Compiler Warning (level 3) CS0219. In Visual C# 2008, no warning is produced.

ushort? usq2 = (byte?)0;

28

Overload resolution

An error is now produced when ambiguous overload resolution occurs on anonymous methods.

Method invocations on overloaded methods must be resolved by the compiler to determine which specific overload to invoke. When an invocation’s parameter type is partially inferred, the specific overload to invoke can become ambiguous. This causes a compiler error.

In the case of an anonymous method being passed as a delegate parameter, the anonymous method’s delegate type is partially inferred. This can lead to ambiguity when the compiler is selecting the correct overload.

In Visual C# 2005, the compiler does not always produce an error when there is no single best overload for an anonymous method. In Visual C# 2008, Compiler Error CS0121 is produced, as shown in the following example.

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

Type errors

An error is now produced if you declare an array of pointers to managed types.

Unsafe pointers to reference types are not allowed, and they cause compiler errors. In Visual C# 2005, it is possible to declare an array of pointers to managed types. In Visual C# 2008, Compiler Error CS0208 is produced: "Cannot take the address of, get the size of, or declare a pointer to a managed type ('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

Overload resolution

A warning is now produced when overload resolution candidate methods vary by only ref or out.

In Visual C# 2005, when the C# compiler performs overload resolution on generic types, it does not verify whether the type arguments will cause the candidate methods to vary by only ref or out. As a result, the choice of methods is left to the common language runtime (CLR) at run time, and it just selects the first method in the list. In Visual C# 2008, Compiler Warning (Level 1) CS1956 is produced when the compiler detects that two candidate methods for overload resolution will vary by only ref or out. This condition is illustrated in the following example.

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

Nullable types and ?? expressions

A null-coalescing expression with null on the left side is no longer evaluated as a null constant.

In Visual C# 2005, a null-coalescing expression with null on the left side is evaluated as a null constant. In Visual C# 2008, this is no longer the case. In some cases, the Visual C# 2005 behavior enables variables to be incorrectly treated as definitely assigned. The following code compiles and runs without error in Visual C# 2005, but in Visual C# 2008, Compiler Error CS0165 is produced: "Use of unassigned local variable 'x'."

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

Date

History

Reason

July 2008

Added topic.

SP1 feature change.

Community Additions

ADD
Show:
© 2014 Microsoft