C# 6 中的新增功能What's New in C# 6

C# 6.0 版本包含许多可提高开发人员工作效率的功能。The 6.0 release of C# contained many features that improve productivity for developers. 这些功能的总体效果是让你编写的代码更简洁、更具可读性。The overall effect of these features is that you write more concise code that is also more readable. 该语法不像许多常见做法那样繁琐。The syntax contains less ceremony for many common practices. 可以更轻松地看出设计意图。It's easier to see the design intent with less ceremony. 好好了解这些功能可以帮助你提高生产力,编写更具可读性的代码。Learn these features well, and you'll be more productive and write more readable code. 你可以更专注于功能,而不是语言的构造。You can concentrate more on your features than on the constructs of the language.

本文的其余部分是对每个功能的概述,并提供用于探索每个功能的链接。The rest of this article provides an overview of each of these features, with a link to explore each feature. 还可以在教程部分的 C# 6 交互式探索中探索这些功能。You can also explore the features in an interactive exploration on C# 6 in the tutorials section.

只读自动属性Read-only auto-properties

只读自动属性 提供了更简洁的语法来创建不可变类型。Read-only auto-properties provide a more concise syntax to create immutable types. 你声明仅具有 get 访问器的自动属性:You declare the auto-property with only a get accessor:

public string FirstName { get; }
public string LastName { get;  }

FirstNameLastName 属性只能在同一个类的构造函数的主体中设置:The FirstName and LastName properties can be set only in the body of the constructor of the same class:

public Student(string firstName, string lastName)
{
    if (IsNullOrWhiteSpace(lastName))
        throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
    FirstName = firstName;
    LastName = lastName;
}

尝试在另一种方法中设置 LastName 会生成 CS0200 编译错误:Trying to set LastName in another method generates a CS0200 compilation error:

public class Student
{
    public string LastName { get;  }

    public void ChangeName(string newLastName)
    {
        // Generates CS0200: Property or indexer cannot be assigned to -- it is read only
        LastName = newLastName;
    }
}

此功能实现用于创建不可变类型的真正语言支持且使用更简洁和方便的自动属性语法。This feature enables true language support for creating immutable types and uses the more concise and convenient auto-property syntax.

如果添加此语法不会删除可访问的方法,则为二进制兼容的更改If adding this syntax doesn't remove an accessible method, it's a binary compatible change.

自动属性初始化表达式Auto-property initializers

自动属性初始值设定项 可让你在属性声明中声明自动属性的初始值。Auto-property initializers let you declare the initial value for an auto-property as part of the property declaration.

public ICollection<double> Grades { get; } = new List<double>();

Grades 成员在声明它的位置处被初始化。The Grades member is initialized where it's declared. 这样,就能更容易地仅执行一次初始化。That makes it easier to perform the initialization exactly once. 初始化是属性声明的一部分,可更轻松地将存储分配等同于 Student 对象的公用接口。The initialization is part of the property declaration, making it easier to equate the storage allocation with the public interface for Student objects.

Expression-bodied 函数成员Expression-bodied function members

你编写的许多成员是可以作为单个表达式的单个语句。Many members that you write are single statements that could be single expressions. 改为编写 expression-bodied 成员。Write an expression-bodied member instead. 这适用于方法和只读属性。It works for methods and read-only properties. 例如,重写 ToString() 通常是理想之选:For example, an override of ToString() is often a great candidate:

public override string ToString() => $"{LastName}, {FirstName}";

也可以将此语法用于只读属性:You can also use this syntax for read-only properties:

public string FullName => $"{FirstName} {LastName}";

将现有成员更改为 expression bodied 成员是二进制兼容的更改Changing an existing member to an expression bodied member is a binary compatible change.

using staticusing static

using static 增强功能可用于导入单个类的静态方法。The using static enhancement enables you to import the static methods of a single class. 指定要使用的类:You specify the class you're using:

using static System.Math;

Math 不包含任何实例方法。The Math does not contain any instance methods. 还可以使用 using static 为具有静态和实例方法的类导入类的静态方法。You can also use using static to import a class' static methods for a class that has both static and instance methods. 最有用的示例之一是 StringOne of the most useful examples is String:

using static System.String;

备注

在 static using 语句中必须使用完全限定的类名 System.StringYou must use the fully qualified class name, System.String in a static using statement. 而不能使用 string 关键字。You cannot use the string keyword instead.

static using 语句导入时,仅在使用扩展方法调用语法调用扩展方法时,扩展方法才在范围内。When imported from a static using statement, extension methods are only in scope when called using the extension method invocation syntax. 作为静态方法调用时,扩展方法不在范围内。They aren't in scope when called as a static method. 你在 LINQ 查询中会经常看到这种情况。You'll often see this in LINQ queries. 可以通过导入 EnumerableQueryable 来导入 LINQ 模式。You can import the LINQ pattern by importing Enumerable, or Queryable.

using static System.Linq.Enumerable;

通常使用扩展方法调用表达式调用扩展方法。You typically call extension methods using extension method invocation expressions. 在使用静态方法调用语法对其进行调用的罕见情况下,添加类名称可以解决歧义。Adding the class name in the rare case where you call them using static method call syntax resolves ambiguity.

static using 指令还可以导入任何嵌套的类型。The static using directive also imports any nested types. 可以引用任何嵌套的类型,而无需限定。You can reference any nested types without qualification.

Null 条件运算符Null-conditional operators

Null 条件运算符使 null 检查更轻松、更流畅 。The null conditional operator makes null checks much easier and fluid. 将成员访问 . 替换为 ?.Replace the member access . with ?.:

var first = person?.FirstName; 

在前面的示例中,如果 Person 对象是 null,则将变量 first 赋值为 nullIn the preceding example, the variable first is assigned null if the person object is null. 否则,将 FirstName 属性的值分配给该变量。Otherwise, it is assigned the value of the FirstName property. 最重要的是,?. 意味着当 person 变量为 null 时,此行代码不会生成 NullReferenceExceptionMost importantly, the ?. means that this line of code doesn't generate a NullReferenceException if the person variable is null. 它会短路并返回 nullInstead, it short-circuits and returns null. 还可以将 null 条件运算符用于数组或索引器访问。You can also use a null conditional operator for array or indexer access. 将索引表达式中的 [] 替换为 ?[]Replace [] with ?[] in the index expression.

无论 person 的值是什么,以下表达式均返回 stringThe following expression returns a string, regardless of the value of person. 通常,将此构造与“null 合并”运算符一起使用,以在其中一个属性为 null 时分配默认值 。You often use this construct with the null coalescing operator to assign default values when one of the properties is null. 表达式短路时,键入返回的 null 值以匹配整个表达式。When the expression short-circuits, the null value returned is typed to match the full expression.

first = person?.FirstName ?? "Unspecified";

还可以将 ?. 用于有条件地调用方法。You can also use ?. to conditionally invoke methods. 具有 null 条件运算符的成员函数的最常见用法是用于安全地调用可能为 null 的委托(或事件处理程序)。The most common use of member functions with the null conditional operator is to safely invoke delegates (or event handlers) that may be null. 通过使用 ?. 运算符调用该委托的 Invoke 方法来访问成员。You'll call the delegate's Invoke method using the ?. operator to access the member. 可以在委托模式一文中看到示例。You can see an example in the delegate patterns article.

?. 运算符的规则确保运算符的左侧仅计算一次。The rules of the ?. operator ensure that the left-hand side of the operator is evaluated only once. 它支持许多语法,包括使用事件处理程序的以下示例:It enables many idioms, including the following example using event handlers:

// preferred in C# 6:
this.SomethingHappened?.Invoke(this, eventArgs);

确保左侧只计算一次,这使得你可以在 ?. 的左侧使用任何表达式(包括方法调用)Ensuring that the left side is evaluated only once also enables you to use any expression, including method calls, on the left side of the ?.

字符串内插String interpolation

使用 C# 6,新的字符串内插功能可以在字符串中嵌入表达式。With C# 6, the new string interpolation feature enables you to embed expressions in a string. 使用 $ 作为字符串的开头,并使用 {} 之间的表达式代替序号:Simply preface the string with $and use expressions between { and } instead of ordinals:

public string FullName => $"{FirstName} {LastName}";

本示例使用替代表达式的属性。This example uses properties for the substituted expressions. 可以使用任何表达式。You can use any expression. 例如,可以在内插过程中计算学生的成绩平均值:For example, you could compute a student's grade point average as part of the interpolation:

public string GetGradePointPercentage() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";

上一行代码将 Grades.Average() 的值格式设置为具有两位小数的浮点数。The preceding line of code formats the value for Grades.Average() as a floating-point number with two decimal places.

通常,可能需要使用特定区域性设置生成的字符串的格式。Often, you may need to format the string produced using a specific culture. 请利用通过字符串内插生成的对象可以隐式转换为 System.FormattableString 这一事实。You use the fact that the object produced by a string interpolation can be implicitly converted to System.FormattableString. FormattableString 实例包含组合格式字符串,以及在将其转换为字符串之前评估表达式的结果。The FormattableString instance contains the composite format string and the results of evaluating the expressions before converting them to strings. 在设置字符串的格式时,可以使用 FormattableString.ToString(IFormatProvider) 方法指定区域性。Use the FormattableString.ToString(IFormatProvider) method to specify the culture when formatting a string. 下面的示例使用德语 (de-DE) 区域性生成字符串。The following example produces a string using the German (de-DE) culture. (德语区域性默认使用“,”字符作为小数分隔符,使用“.”字符作为千位分隔符。)(By default, the German culture uses the ',' character for the decimal separator, and the '.' character as the thousands separator.)

FormattableString str = $"Average grade is {s.Grades.Average()}";
var gradeStr = str.ToString(new System.Globalization.CultureInfo("de-DE"));

要开始使用字符串内插,请参阅 C# 中的字符串内插交互式教程、字符串内插一文和 C# 中字符串内插教程。To get started with string interpolation, see the String interpolation in C# interactive tutorial, the String interpolation article, and the String interpolation in C# tutorial.

异常筛选器Exception filters

“异常筛选器”是确定何时应该应用给定的 catch 子句的子句 。Exception Filters are clauses that determine when a given catch clause should be applied. 如果用于异常筛选器的表达式计算结果为 true,则 catch 子句将对异常执行正常处理。If the expression used for an exception filter evaluates to true, the catch clause performs its normal processing on an exception. 如果表达式计算结果为 false,则将跳过 catch 子句。If the expression evaluates to false, then the catch clause is skipped. 一种用途是检查有关异常的信息,以确定 catch 子句是否可以处理该异常:One use is to examine information about an exception to determine if a catch clause can process the exception:

public static async Task<string> MakeRequest()
{
    WebRequestHandler webRequestHandler = new WebRequestHandler();
    webRequestHandler.AllowAutoRedirect = false;
    using (HttpClient client = new HttpClient(webRequestHandler))
    {
        var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/");
        try
        {
            var responseText = await stringTask;
            return responseText;
        }
        catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
        {
            return "Site Moved";
        }
    }
}

nameof 表达式The nameof expression

nameof 表达式的计算结果为符号的名称。The nameof expression evaluates to the name of a symbol. 每当需要变量、属性或成员字段的名称时,这是让工具正常运行的好办法。It's a great way to get tools working whenever you need the name of a variable, a property, or a member field. nameof 的其中一个最常见的用途是提供引起异常的符号的名称:One of the most common uses for nameof is to provide the name of a symbol that caused an exception:

if (IsNullOrWhiteSpace(lastName))
    throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));

另一个用途是用于实现 INotifyPropertyChanged 接口的基于 XAML 的应用程序:Another use is with XAML-based applications that implement the INotifyPropertyChanged interface:

public string LastName
{
    get { return lastName; }
    set
    {
        if (value != lastName)
        {
            lastName = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(LastName)));
        }
    }
}
private string lastName;

Catch 和 Finally 块中的 AwaitAwait in Catch and Finally blocks

C# 5 对于可放置 await 表达式的位置有若干限制。C# 5 had several limitations around where you could place await expressions. 使用 C# 6,现在可以在 catchfinally 表达式中使用 awaitWith C# 6, you can now use await in catch or finally expressions. 这通常用于日志记录方案:This is most often used with logging scenarios:

public static async Task<string> MakeRequestAndLogFailures()
{ 
    await logMethodEntrance();
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
    {
        await logError("Recovered from redirect", e);
        return "Site Moved";
    }
    finally
    {
        await logMethodExit();
        client.Dispose();
    }
}

catchfinally 子句中添加 await 支持的实现细节可确保该行为与同步代码的行为一致。The implementation details for adding await support inside catch and finally clauses ensure that the behavior is consistent with the behavior for synchronous code. 当在 catchfinally 子句中执行的代码引发异常时,执行将在下一个外层块中查找合适的 catch 子句。When code executed in a catch or finally clause throws, execution looks for a suitable catch clause in the next surrounding block. 如果存在当前异常,则该异常将丢失。If there was a current exception, that exception is lost. catchfinally 子句中的 awaited 表达式也会发生同样的情况:搜索合适的 catch,并且当前异常(如果有)将丢失。The same happens with awaited expressions in catch and finally clauses: a suitable catch is searched for, and the current exception, if any, is lost.

备注

鉴于此行为,建议仔细编写 catchfinally 子句,避免引入新的异常。This behavior is the reason it's recommended to write catch and finally clauses carefully, to avoid introducing new exceptions.

使用索引器初始化关联集合Initialize associative collections using indexers

索引初始值设定项 是提高集合初始值设定项与索引用途一致性的两个功能之一。Index Initializers is one of two features that make collection initializers more consistent with index usage. 在早期版本的 C# 中,可以将集合初始值设定项用于序列样式集合,包括在键值对周围添加括号而得到 Dictionary<TKey,TValue>In earlier releases of C#, you could use collection initializers with sequence style collections, including Dictionary<TKey,TValue>, by adding braces around key and value pairs:

private Dictionary<int, string> messages = new Dictionary<int, string>
{
    { 404, "Page not Found"},
    { 302, "Page moved, but left a forwarding address."},
    { 500, "The web server can't come out to play today."}
};

可以将集合初始值设定项与 Dictionary<TKey,TValue> 集合和其他类型一起使用,在这种情况下,可访问的 Add 方法接受多个参数。You can use them with Dictionary<TKey,TValue> collections and other types where the accessible Add method accepts more than one argument. 新语法支持使用索引分配到集合中:The new syntax supports assignment using an index into the collection:

private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
    [404] = "Page not Found",
    [302] = "Page moved, but left a forwarding address.",
    [500] = "The web server can't come out to play today."
};

此功能意味着,可以使用与多个版本中已有的序列容器语法类似的语法初始化关联容器。This feature means that associative containers can be initialized using syntax similar to what's been in place for sequence containers for several versions.

集合初始值设定项中的扩展 Add 方法Extension Add methods in collection initializers

使集合初始化更容易的另一个功能是对 Add 方法使用扩展方法 。Another feature that makes collection initialization easier is the ability to use an extension method for the Add method. 添加此功能的目的是进行 Visual Basic 的奇偶校验。This feature was added for parity with Visual Basic. 如果自定义集合类的方法具有通过语义方式添加新项的名称,则此功能非常有用。The feature is most useful when you have a custom collection class that has a method with a different name to semantically add new items.

改进了重载解析Improved overload resolution

你可能不会注意到这最后一项功能。This last feature is one you probably won't notice. 在以前的一些构造中,以前版本的 C# 编译器可能会发现涉及 lambda 表达式的一些方法不明确。There were constructs where the previous version of the C# compiler may have found some method calls involving lambda expressions ambiguous. 请考虑此方法:Consider this method:

static Task DoThings() 
{
     return Task.FromResult(0); 
}

在早期版本的 C# 中,使用方法组语法调用该方法将失败:In earlier versions of C#, calling that method using the method group syntax would fail:

Task.Run(DoThings); 

早期的编译器无法正确区分 Task.Run(Action)Task.Run(Func<Task>())The earlier compiler couldn't distinguish correctly between Task.Run(Action) and Task.Run(Func<Task>()). 在早期版本中,需要使用 lambda 表达式作为参数:In previous versions, you'd need to use a lambda expression as an argument:

Task.Run(() => DoThings());

C# 6 编译器正确地确定 Task.Run(Func<Task>()) 是更好的选择。The C# 6 compiler correctly determines that Task.Run(Func<Task>()) is a better choice.

确定性的编译器选项Deterministic compiler output

-deterministic 选项指示编译器为同一源文件的后续编译生成完全相同的输出程序集。The -deterministic option instructs the compiler to produce a byte-for-byte identical output assembly for successive compilations of the same source files.

默认情况下,每个编译都生成唯一的输出内容。By default, every compilation produces unique output on each compilation. 编译器添加一个时间戳和一个随机生成的 GUID。The compiler adds a timestamp, and a GUID generated from random numbers. 如果想按字节比较输出以确保各项生成之间的一致性,请使用此选项。You use this option if you want to compare the byte-for-byte output to ensure consistency across builds.

有关详细信息,请参阅 -deterministic 编译器选项文章。For more information, see the -deterministic compiler option article.