新语法概述;增加了哪些新语法特性
1、语法的作用,是干啥的
2、描述有此语法之前怎么实现;利用新语法怎么实现
3、实例代码
参考链接
https://www.cnblogs.com/andy626/p/13826942.html
https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-version-history
C# 5.0
Asynchronous methods:异步方法 Caller info attributes:调用方信息特性,调用时访问调用者的信息
异步方法
(异步成员、异步编程)(此版本主角)
异步编程的核心是 Task 和 Task<T> 对象,这两个对象对异步操作建模。 它们受关键字 async 和 await 的支持。 在大多数情况下模型十分简单: 对于 I/O 绑定代码,等待一个在 async 方法中返回 Task 或 Task<T> 的操作。 对于 CPU 绑定代码,等待一个使用 Task.Run 方法在后台线程启动的操作。 它控制执行 await 的方法的调用方,且它最终允许 UI 具有响应性或服务具有灵活性。异步编程遵循基于任务的异步模式 (TAP)。
private readonly HttpClient _httpClient = new HttpClient(); [HttpGet, Route("DotNetCount")] public async Task<int> GetDotNetCount() { // Suspends GetDotNetCount() to allow the caller (the web server) // to accept another request, rather than blocking on this one. var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org"); return Regex.Matches(html, @".NET").Count; }
此代码片段从 https://dotnetfoundation.org 主页下载 HTML,并计算 HTML 中字符串“.NET”的出现次数。
private async void button1_Click(object sender, EventArgs e) { var t = Task.Run(() => { Thread.Sleep(5000); return "Hello I am TimeConsumingMethod"; }); textBox1.Text = await t; }
注意
async 方法需要在主体中有 await 关键字,否则它们将永不暂停 应将“Async”作为后缀添加到所编写的每个异步方法名称中
异步方法的返回类型只能是void、Task、Task<TResult>
async void 应仅用于事件处理程序,方法中引发的异常无法在该方法外部被捕获,很难测试,还可能会导致不良副作用(如果调用方不希望方法是异步的话) 在 LINQ 表达式中使用异步 lambda 时请谨慎 采用非阻止方式编写等待任务的代码,如使用await检索后台任务的结果,而不是Task.Wait 或 Task.Result。
调用方信息特性,调用时访问调用者的信息
public void DoProcessing() { TraceMessage("Something happened."); } public void TraceMessage(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { Trace.WriteLine("message: " + message); Trace.WriteLine("member name: " + memberName); Trace.WriteLine("source file path: " + sourceFilePath); Trace.WriteLine("source line number: " + sourceLineNumber); } // Sample Output: // message: Something happened. // member name: DoProcessing // source file path: c:Visual Studio ProjectsCallerInfoCSCallerInfoCSForm1.cs // source line number: 31
为每个可选参数指定显式默认值。 不能将调用方信息特性应用于未指定为可选的参数。 调用方信息特性不会使参数成为可选参数。 相反,它们会在忽略此参数时影响传入的默认值
C# 6.0
using 静态指令
using static 增强功能可用于导入单个类的静态方法。
指定要使用的类: using static System.Math;
原来的写法
public double Circumference { get { return 2 * Radius * Math.PI; } } using static System.Math; public double Circumference { get { return 2 * Radius * PI; } }
异常过滤器 when
可以使用上下文关键字 when 在以下上下文中指定筛选条件: 在 try/catch 或 try/catch/finally 块的 catch 语句中。 在 switch 语句的 case 标签中。
catch 语句中的 when 以指定为执行特定异常处理程序而必须为 true 的条件。 语法为: catch (ExceptionType [e]) when (expr)
用法如
try { var responseText = await streamTask; return responseText; } catch (HttpRequestException e) when (e.Message.Contains("301")) { return "Site Moved"; } catch (HttpRequestException e) when (e.Message.Contains("404")) { return "Page Not Found"; }
switch 语句中的 when 从 C# 7.0 开始,case 标签无需是互斥的,且 case 标签在 switch 语句中的显示顺序可以决定要执行的 switch 块。 when 关键字可指定一个筛选条件,该条件使得仅当筛选条件也为 true 时,与其相关联的 case 标签才为 true.
自动属性初始化表达式
get 只读属性 简洁的语法来创建不可变类型,
仅有get访问器:
public string FirstName { get; } public string LastName { get; }
也可以使用的是私有化来设置set。
然后通过构造函数来赋值:
public Student(string firstName, string lastName) { if (IsNullOrWhiteSpace(lastName)) throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName)); FirstName = firstName; LastName = lastName; }
get 属性初始化表达式
在属性声明中声明自动属性的初始值, public string FirstName { get; set; } = string.Empty;
Expression-bodied 函数成员 适用于方法和只读属性。 例如,重写 ToString() 通常是理想之选: public override string ToString() => $"{LastName}, {FirstName}"; 也可以将此语法用于只读属性: public string FullName => $"{FirstName} {LastName}";
Null 条件运算符
Null 条件运算符使 null 检查更轻松、更流畅 。 将成员访问 . 替换为 ?.: var first = person?.FirstName; 如果person为空,返回的值就是null,是string的默认值,如果FirstName是int类型,那返回的就是int的默认值0。
$ 字符串内插
新的字符串内插功能可以在字符串中嵌入表达式。 使用 $ 作为字符串的开头,并使用 { 和 } 之间的表达式代替序号:
public string GetGradePointPercentage() =>$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";
上一行代码将 Grades.Average() 的值格式设置为具有两位小数的浮点数。
public string GetGradePointPercentage() =>$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";
上一行代码将 Grades.Average() 的值格式设置为具有两位小数的浮点数
string name = "Mark"; var date = DateTime.Now; // Composite formatting: Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date); // String interpolation: Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now."); // Both calls produce the same output that is similar to: // Hello, Mark! Today is Wednesday, it's 19:40 now.
nameof 表达式
nameof 表达式的计算结果为符号的名称。 每当需要变量、属性或成员字段的名称时,这是让工具正常运行的好办法,说白了就是更好的重构:
if (IsNullOrWhiteSpace(lastName)) throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
Catch 和 Finally 块中的 Await
可以在 catch 或 finally 表达式中使用 await。 这通常用于日志记录方案:
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(); } }
索引器初始化关联集合
可以将集合初始值设定项与 Dictionary<TKey,TValue> 集合和其他类型一起使用,在这种情况下,可访问的 Add 方法接受多个参数。 新语法支持使用索引分配到集合中:
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." };
此版本还发布了 Roslyn 编译器即服务
C# 7.0
out 变量
可以在方法调用的参数列表中声明 out 变量,而不是编写单独的声明语句:
if (int.TryParse(input, out int result)) Console.WriteLine(result); else Console.WriteLine("Could not parse input")
为清晰明了,可能需指定 out 变量的类型,如上所示。 但是,该语言支持使用隐式类型的局部变量:
if (int.TryParse(input, out var answer)) Console.WriteLine(answer); else Console.WriteLine("Could not parse input");
Tuple 元组
低于 C# 7.0 的版本中也提供元组,但它们效率低下且不具有语言支持。这意味着元组元素只能作为 Item1 和 Item2 等引用。
可以通过为每个成员赋值来创建元组,并可选择为元组的每个成员提供语义名称:
(string Alpha, string Beta) namedLetters = ("a", "b"); Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}"); 在进行元组赋值时,还可以指定赋值右侧的字段的名称:
var alphabetStart = (Alpha: "a", Beta: "b"); Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}"); 使用的时候,可以直接点出来: alphabetStart.Alpha alphabetStart.Beta
_ 弃元
C# 增添了对弃元的支持。 弃元是一个名为 _(下划线字符)的只写变量,可向单个变量赋予要放弃的所有值。 弃元类似于未赋值的变量;不可在代码中使用弃元(赋值语句除外):
public class Example { public static void Main() { var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010); Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}"); } private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2) { int population1 = 0, population2 = 0; double area = 0; if (name == "New York City") { area = 468.48; if (year1 == 1960) { population1 = 7781984; } if (year2 == 2010) { population2 = 8175133; } return (name, area, year1, population1, year2, population2); } return ("", 0, 0, 0, 0, 0); } }
is 模式匹配
模式匹配支持 is 表达式和 switch 表达式。 每个表达式都允许检查对象及其属性以确定该对象是否满足所寻求的模式。 使用 when 关键字来指定模式的其他规则:
public static int SumPositiveNumbers(IEnumerable<object> sequence) { int sum = 0; foreach (var i in sequence) { switch (i) { case 0: break; case IEnumerable<int> childSequence: { foreach(var item in childSequence) sum += (item > 0) ? item : 0; break; } case int n when n > 0: sum += n; break; case null: throw new NullReferenceException("Null found in sequence"); default: throw new InvalidOperationException("Unrecognized type"); } } return sum; }
case 0: 是常见的常量模式。
case IEnumerable<int> childSequence: 是一种类型模式。
case int n when n > 0: 是具有附加 when 条件的类型模式。
case null: 是 null 模式。
default: 是常见的默认事例。
本地函数(内部)
本地函数使你能够在另一个方法的上下文内声明方法。 本地函数使得类的阅读者更容易看到本地方法仅从声明它的上下文中调用。
public static IEnumerable<char> AlphabetSubset3(char start, char end) { if (start < 'a' || start > 'z') throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); if (end < 'a' || end > 'z') throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); if (end <= start) throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); return alphabetSubsetImplementation(); IEnumerable<char> alphabetSubsetImplementation() { for (var c = start; c < end; c++) yield return c; } }
注意上边的alphabetSubsetImplementation方法,是在内部定义的。 同样可以使用异步:
public Task<string> PerformLongRunningWork(string address, int index, string name) { if (string.IsNullOrWhiteSpace(address)) throw new ArgumentException(message: "An address is required", paramName: nameof(address)); if (index < 0) throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException(message: "You must supply a name", paramName: nameof(name)); return longRunningWorkImplementation(); async Task<string> longRunningWorkImplementation() { var interimResult = await FirstWork(address); var secondResult = await SecondStep(index, name); return $"The results are {interimResult} and {secondResult}. Enjoy."; } }
更多的 expression-bodied 成员
在 C# 7.0 中,你可以在属性和索引器上实现构造函数、终结器以及 get 和 set 访问器。 以下代码演示了每种情况的示例:
// Expression-bodied constructor public ExpressionMembersExample(string label) => this.Label = label; // Expression-bodied finalizer ~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!"); private string label; // Expression-bodied get / set accessors. public string Label { get => label; set => this.label = value ?? "Default label"; }
C# 7.1
异步 main 方法
异步 Main 方法使你能够在 Main 方法中使用 await 关键字。 在过去,需要编写:
static int Main() { return DoAsyncWork().GetAwaiter().GetResult(); }
现在,可以编写
static async Task<int> Main() { // This could also be replaced with the body // DoAsyncWork, including its await expressions: return await DoAsyncWork(); }
如果程序不返回退出代码,可以声明返回 Task 的 Main 方法:
static async Task Main() { await SomeAsyncMethod(); }
默认文本表达式
默认文本表达式是针对默认值表达式的一项增强功能。 这些表达式将变量初始化为默认值。 过去会这么编写: Func<string, bool> whereClause = default(Func<string, bool>); 现在,可以省略掉初始化右侧的类型: Func<string, bool> whereClause = default;
C# 7.2
启用更高效的安全代码
无需固定即可访问固定的字段。 可以重新分配 ref 本地变量。 可以使用 stackalloc 数组上的初始值设定项。 可以对支持模式的任何类型使用 fixed 语句。 可以使用其他泛型约束。 针对实参的 in 修饰符,指定形参通过引用传递,但不通过调用方法修改。 将 in 修饰符添加到参数是源兼容的更改。 针对方法返回的 ref readonly 修饰符,指示方法通过引用返回其值,但不允许写入该对象。 如果向某个值赋予返回值,则添加 ref readonly 修饰符是源兼容的更改。 将 readonly 修饰符添加到现有的 ref 返回语句是不兼容的更改。 它要求调用方更新 ref 本地变量的声明以包含 readonly 修饰符。 readonly struct 声明,指示结构不可变,且应作为 in 参数传递到其成员方法。 将 readonly 修饰符添加到现有的结构声明是二进制兼容的更改。 ref struct 声明,指示结构类型直接访问托管的内存,且必须始终分配有堆栈。 将 ref 修饰符添加到现有 struct 声明是不兼容的更改。 ref struct 不能是类的成员,也不能用于可能在堆上分配的其他位置。
非尾随命名参数
方法调用现可使用位于位置参数前面的命名参数(若这些命名参数的位置正确)。
private protected 访问修饰符
新的复合访问修饰符:private protected 指示可通过包含同一程序集中声明的类或派生类来访问成员。 虽然 protected internal 允许通过同一程序集中的类或派生类进行访问,但 private protected 限制对同一程序集中声明的派生类的访问。
条件 ref 表达式
最后,条件表达式可能生成 ref 结果而不是值。 例如,你将编写以下内容以检索对两个数组之一中第一个元素的引用: ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]); 变量 r 是对 arr 或 otherArr 中第一个值的引用。
数字文本语法改进
C# 7.0 包括两项新功能,可用于以最可读的方式写入数字来用于预期用途:二进制文本和数字分隔符 。在创建位掩码时,或每当数字的二进制表示形式使代码最具可读性时,以二进制形式写入该数字:
public const int Sixteen = 0b0001_0000; public const int ThirtyTwo = 0b0010_0000; public const int SixtyFour = 0b0100_0000; public const int OneHundredTwentyEight = 0b1000_0000;
常量开头的 0b 表示该数字以二进制数形式写入。 二进制数可能会很长,因此通过引入 _ 作为数字分隔符通常更易于查看位模式,如上面二进制常量所示。 数字分隔符可以出现在常量的任何位置。 对于十进制数字,通常将其用作千位分隔符:
public const long BillionsAndBillions = 100_000_000_000;
C# 7.3
索引 fixed 字段不需要进行固定
请考虑此结构:
unsafe struct S { public fixed int myFixedField[10]; }
在早期版本的 C# 中,需要固定变量才能访问属于 myFixedField 的整数之一。 现在,以下代码进行编译,而不将变量 p 固定到单独的 fixed 语句中:
class C { static S s = new S(); unsafe public void M() { int p = s.myFixedField[5]; } }
变量 p 访问 myFixedField 中的一个元素。 无需声明单独的 int* 变量。 仍需要 unsafe 上下文。 在早期版本的 C# 中,需要声明第二个固定的指针:
class C { static S s = new S(); unsafe public void M() { fixed (int* ptr = s.myFixedField) { int p = ptr[5]; } } }
stackalloc 数组支持初始值设定项
当你对数组中的元素的值进行初始值设定时,你已能够指定该值:
var arr = new int[3] {1, 2, 3};
var arr2 = new int[] {1, 2, 3};
现在,可向使用 stackalloc 进行声明的数组应用同一语法:
int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};
更多类型支持 fixed 语句
fixed 语句支持有限的一组类型。 从 C# 7.3 开始,任何包含返回 ref T 或 ref readonly T 的 GetPinnableReference() 方法的类型均有可能为 fixed。 添加此功能意味着 fixed 可与 System.Span<T> 和相关类型配合使用。
C# 8.0
“.NET Core 3.x”和“.NET Standard 2.1”支持 C# 8.0;
Readonly 成员
可将 readonly 修饰符应用于结构的成员。 它指示该成员不会修改状态。 这比将 readonly 修饰符应用于 struct 声明更精细。
请考虑以下可变结构:
public readonly double Distance => Math.Sqrt(X * X + Y * Y);
默认接口方法
现在可以将成员添加到接口,并为这些成员提供实现。 借助此语言功能,API 作者可以将方法添加到以后版本的接口中,而不会破坏与该接口当前实现的源或二进制文件兼容性。 现有的实现继承默认实现。
public interface ICustomer { IEnumerable<IOrder> PreviousOrders { get; } DateTime DateJoined { get; } DateTime? LastOrder { get; } string Name { get; } IDictionary<DateTime, string> Reminders { get; } }
Switch 表达式升级
通常情况下,switch 语句在其每个 case 块中生成一个值。借助 Switch 表达式,可以使用更简洁的表达式语法。
public static RGBColor FromRainbowClassic(Rainbow colorBand) { switch (colorBand) { case Rainbow.Red: return new RGBColor(0xFF, 0x00, 0x00); case Rainbow.Orange: return new RGBColor(0xFF, 0x7F, 0x00); case Rainbow.Yellow: return new RGBColor(0xFF, 0xFF, 0x00); case Rainbow.Green: return new RGBColor(0x00, 0xFF, 0x00); case Rainbow.Blue: return new RGBColor(0x00, 0x00, 0xFF); case Rainbow.Indigo: return new RGBColor(0x4B, 0x00, 0x82); case Rainbow.Violet: return new RGBColor(0x94, 0x00, 0xD3); default: throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)); }; }
这里有几个语法改进: 变量位于 switch 关键字之前。
不同的顺序使得在视觉上可以很轻松地区分 switch 表达式和 switch 语句。
将 case 和 : 元素替换为 =>。 它更简洁,更直观。 将 default 事例替换为 _ 弃元。
正文是表达式,不是语句。
public static RGBColor FromRainbow(Rainbow colorBand) => colorBand switch { Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00), Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00), Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00), Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00), Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF), Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82), Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3), _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)), };
属性模式
借助属性模式,可以匹配所检查的对象的属性。 请看一个电子商务网站的示例,该网站必须根据买家地址计算销售税。 这种计算不是 Address 类的核心职责。 它会随时间变化,可能比地址格式的更改更频繁。 销售税的金额取决于地址的 State 属性。
下面的方法使用属性模式从地址和价格计算销售税:
public static decimal ComputeSalesTax(Address location, decimal salePrice) => location switch { { State: "WA" } => salePrice * 0.06M, { State: "MN" } => salePrice * 0.075M, { State: "MI" } => salePrice * 0.05M, // other cases removed for brevity... _ => 0M };
在 LINQ 查询中会经常看到这种情况。 可以通过导入 Enumerable 或 Queryable 来导入 LINQ 模式。
元组模式
一些算法依赖于多个输入。 使用元组模式,可根据表示为元组的多个值进行切换。 以下代码显示了游戏“rock, paper, scissors(石头剪刀布)”的切换表达式:
public static string RockPaperScissors(string first, string second) => (first, second) switch { ("rock", "paper") => "rock is covered by paper. Paper wins.", ("rock", "scissors") => "rock breaks scissors. Rock wins.", ("paper", "rock") => "paper covers rock. Paper wins.", ("paper", "scissors") => "paper is cut by scissors. Scissors wins.", ("scissors", "rock") => "scissors is broken by rock. Rock wins.", ("scissors", "paper") => "scissors cuts paper. Scissors wins.", (_, _) => "tie" };
如果person为空,返回的值就是null,是string的默认值,如果FirstName是int类型,那返回的就是int的默认值0。
using 声明
using 声明是前面带 using 关键字的变量声明。 它指示编译器声明的变量应在封闭范围的末尾进行处理。 以下面编写文本文件的代码为例:
static int WriteLinesToFile(IEnumerable<string> lines) { using var file = new System.IO.StreamWriter("WriteLines2.txt"); // Notice how we declare skippedLines after the using statement. int skippedLines = 0; foreach (string line in lines) { if (!line.Contains("Second")) { file.WriteLine(line); } else { skippedLines++; } } // Notice how skippedLines is in scope here. return skippedLines; // file is disposed here }
前面的代码相当于下面使用经典 using 语句的代码:
static int WriteLinesToFile(IEnumerable<string> lines) { // We must declare the variable outside of the using block // so that it is in scope to be returned. int skippedLines = 0; using (var file = new System.IO.StreamWriter("WriteLines2.txt")) { foreach (string line in lines) { if (!line.Contains("Second")) { file.WriteLine(line); } else { skippedLines++; } } return skippedLines; } // file is disposed here }
Static 静态本地函数
现在可以向本地函数添加 static 修饰符,以确保本地函数不会从封闭范围捕获(引用)任何变量。
int M() { int y = 5; int x = 7; return Add(x, y); static int Add(int left, int right) => left + right; }
async 异步流
从 C# 8.0 开始,可以创建并以异步方式使用流。返回异步流的方法有三个属性: 它是用 async 修饰符声明的。 它将返回 IAsyncEnumerable<T>。 该方法包含用于在异步流中返回连续元素的 yield return 语句。
public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; } } await foreach (var number in GenerateSequence()) { Console.WriteLine(number); }
异步可释放: 从 C# 8.0 开始,语言支持实现 System.IAsyncDisposable 接口的异步可释放类型。可使用 await using 语句来处理异步可释放对象。
索引和范围
范围指定范围的开始和末尾 。 包括此范围的开始,但不包括此范围的末尾,这表示此范围包含开始但不包含末尾 。 范围 [0..^0] 表示整个范围,就像 [0..sequence.Length] 表示整个范围。
以下示例,请考虑以下数组,用其顺数索引和倒数索引进行注释:
var words = new string[] { // index from start index from end "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0 // 可以使用 ^1 索引检索最后一个词: Console.WriteLine($"The last word is {words[^1]}"); // writes "dog"
以下代码创建了一个包含单词“quick”、“brown”和“fox”的子范围。 它包括 words[1] 到 words[3]。 元素 words[4] 不在该范围内。
var quickBrownFox = words[1..4]; var allWords = words[..]; // contains "The" through "dog". var firstPhrase = words[..4]; // contains "The" through "fox" var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
null 合并赋值
C# 8.0 引入了 null 合并赋值运算符 ??=。 仅当左操作数计算为 null 时,才能使用运算符 ??= 将其右操作数的值分配给左操作数。
List<int> numbers = null; int? i = null; numbers ??= new List<int>(); numbers.Add(i ??= 17); numbers.Add(i ??= 20); Console.WriteLine(string.Join(" ", numbers)); // output: 17 17 Console.WriteLine(i); // output: 17
C# 9.0
.NET5支持C#9.0.
记录类型
C# 9.0 引入了记录类型,这是一种引用类型,它提供合成方法来提供值语义,从而实现相等性。 默认情况下,记录是不可变的。
public record Person { public string LastName { get; } public string FirstName { get; } public Person(string first, string last) => (FirstName, LastName) = (first, last); }
Init 仅限的资源库
从 C# 9.0 开始,可为属性和索引器创建 init 访问器,而不是 set 访问器。 调用方可使用属性初始化表达式语法在创建表达式中设置这些值,但构造完成后,这些属性将变为只读。 仅限 init 的资源库提供了一个窗口用来更改状态。
public struct WeatherObservation { public DateTime RecordedAt { get; init; } public decimal TemperatureInCelsius { get; init; } public decimal PressureInMillibars { get; init; } public override string ToString() => $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " + $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure"; }
调用方可使用属性初始化表达式语法来设置值,同时仍保留不变性:
var now = new WeatherObservation { RecordedAt = DateTime.Now, TemperatureInCelsius = 20, PressureInMillibars = 998.0m };
顶级语句
顶级语句从许多应用程序中删除了不必要的流程。只有一行代码执行所有操作。 借助顶级语句,可使用 using 语句和执行操作的一行替换所有样本:
using System;
Console.WriteLine("Hello World!");
如果需要单行程序,可删除 using 指令,并使用完全限定的类型名称:
System.Console.WriteLine("Hello World!");
模式匹配增强功能
C# 9 包括新的模式匹配改进: 类型模式要求在变量是一种类型时匹配 带圆括号的模式强制或强调模式组合的优先级 联合 and 模式要求两个模式都匹配 析取 or 模式要求任一模式匹配 求反 not 模式要求模式不匹配 关系模式要求输入小于、大于、小于等于或大于等于给定常数。
public static bool IsLetter(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z'; public static bool IsLetterOrSeparator(this char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
调试和完成功能
在 C# 9.0 中,已知创建对象的类型时,可在 new 表达式中省略该类型。 最常见的用法是在字段声明中: private List<WeatherObservation> _observations = new(); 当需要创建新对象作为参数传递给方法时,也可使用目标类型 new。 请考虑使用以下签名的 ForecastFor() 方法: public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options) 可按如下所示调用该方法: