龙空技术网

c# 10 教程:1 介绍 C# 和 .NET

启辰8 62

前言:

眼前我们对“net开发触屏版软件”大概比较注意,我们都需要了解一些“net开发触屏版软件”的相关知识。那么小编也在网上搜集了一些有关“net开发触屏版软件””的相关知识,希望各位老铁们能喜欢,我们一起来学习一下吧!

C# 是一种通用、类型安全、面向对象的编程语言。该语言的目标是程序员的生产力。为此,C# 平衡了简单性、表现力和性能。自第一个版本以来,该语言的首席架构师是Anders Hejlsberg(Turbo Pascal的创建者和Delphi的架构师)。C# 语言与平台无关,适用于一系列特定于平台的运行时。

面向对象

C# 是面向对象范例的丰富实现,其中包括、和。封装意味着在周围创建边界,以将其外部(公共)行为与其内部(私有)实现细节分开。从面向对象的角度来看,以下是 C# 的独特功能:

统一型系统

C# 中的基本构建基块是称为数据和函数的封装单元。C# 具有,其中所有类型最终共享一个公共基类型。这意味着所有类型,无论它们表示业务对象还是基元类型(如数字),都共享相同的基本功能。例如,任何类型的实例都可以通过调用其 ToString 方法转换为字符串。

类和接口

在传统的面向对象范式中,唯一的类型是类。在 C# 中,还有其他几种类型,其中一种是。接口就像一个无法保存数据的类。这意味着它只能定义(而不是),这允许多重继承以及规范和实现之间的分离。

属性、方法和事件

在纯面向对象的范式中,所有函数都是。在 C# 中,方法只是一种,它还包括和事件(还有其他属性和)。属性是封装对象状态片段(如按钮的颜色或标签的文本)的函数成员。事件是简化对对象状态更改的操作的函数成员。

尽管 C# 主要是一种面向对象的语言,但它也借鉴了函数式编程范;具体说来:

函数可以被视为值

使用,C# 允许函数作为值传递到其他函数或从其他函数传递。

C# 支持纯度模式

函数式编程的核心是避免使用值发生变化的变量,而支持声明性模式。C# 具有帮助处理这些模式的关键功能,包括能够动态编写“捕获”变量( 表达式)的未命名函数,以及通过执行列表或响应式编程的能力。C# 还提供,这使得编写(只读)类型变得容易。

类型安全

C# 主要是一种语言,这意味着类型的实例只能通过它们定义的协议进行交互,从而确保每个类型的内部一致性。例如,C# 阻止您与类型交互,就好像它是类型一样。

更具体地说,C# 支持静态类型,这意味着该语言在强制实施安全。这是对在强制实施的类型安全的补充。

静态类型甚至在程序运行之前就消除了一大类错误。它将负担从运行时单元测试转移到编译器上,以验证程序中的所有类型是否正确组合在一起。这使得大型程序更易于管理、更可预测且更健壮。此外,静态类型允许Visual Studio中的IntelliSense等工具帮助您编写程序,因为它知道给定变量的类型,因此可以对该变量调用哪些方法。此类工具还可以识别程序中使用变量、类型或方法的任何地方,从而实现可靠的重构。

注意

C# 还允许通过 dynamic 关键字动态键入部分代码。但是,C# 仍然是一种主要是静态类型的语言。

C# 也称为,因为它的类型规则是严格强制执行的(无论是静态还是在运行时)。例如,不能调用旨在接受具有浮点数的整数的函数,浮点数显式转换为整数。这有助于防止错误。

内存管理

C# 依赖于运行时来执行自动内存管理。公共语言运行库有一个垃圾回收器,该回收器作为程序的一部分执行,为不再引用的对象回收内存。这使程序员不必显式地为对象释放内存,从而消除了在C++等语言中遇到的错误指针的问题。

C# 不会消除指针:它只是使大多数编程任务不需要指针。对于性能关键型热点和互操作性,允许在标记为不安全的块中使用指针和显式内存分配。

平台支持

C# 具有支持以下平台的运行时:

Windows Desktop 7-11(适用于富客户端、Web、服务器和命令行应用程序)macOS(适用于富客户端、Web 和命令行应用程序)Linux 和 macOS(用于 Web 和命令行应用程序)安卓和iOS(用于移动应用程序)Windows 10 设备(Xbox、Surface Hub 和 HoloLens)

还有一种称为 的技术,可以将 C# 编译为在浏览器中运行的 Web 程序集。

CLR、BCL 和运行时

C# 程序的运行时支持包括和。运行时还可以包括更高级别的,其中包含用于开发胖客户端、移动或 Web 应用程序的库(参见)。存在不同的运行时以允许不同类型的应用程序以及不同的平台。

公共语言运行库

(CLR) 提供必要的运行时服务,如自动内存管理和异常处理。(“common”一词是指同一运行时可以由其他编程语言(如 F#、Visual Basic 和托管C++)共享的事实。

C# 之所以称为托管语言,是因为它将源代码编译为代码,托管代码以 (IL) 表示。CLR 将 IL 转换为计算机的本机代码,如 X64 或 X86,通常在执行之前。这称为实时 (JIT) 编译。提前编译也可用于缩短大型程序集或资源受限设备的启动时间(并在开发移动应用程序时满足 iOS 应用商店规则)。

托管代码的容器称为。程序集不仅包含 IL,还包含类型信息()。元数据的存在允许程序集引用其他程序集中的类型,而无需其他文件。

注意

您可以使用Microsoft的 工具检查和反汇编程序集的内容。使用ILSpy或JetBrain的dotPeek等工具,您可以更进一步将IL反编译为C#。由于 IL 比本机机器代码级别更高,因此反编译器可以很好地重建原始 C#。

程序可以查询自己的元数据(反射),甚至可以在运行时生成新的 IL()。

基类库

CLR 始终附带一组称为 (BCL) 的程序集。BCL 为程序员提供核心功能,例如集合、输入/输出、文本处理、XML/JSON 处理、网络、加密、互操作、并发和并行编程。

BCL 还实现 C# 语言本身所需的类型(用于枚举、查询和异步等功能),并允许您显式访问 CLR 的功能,如反射和内存管理。

运行时

(也称为)是下载和安装的可部署单元。运行时由 CLR(及其 BCL)以及特定于您正在编写的应用程序类型(Web、移动、富客户端等)的可选应用程序层组成(如果要编写命令行控制台或非 UI 库,则不需要应用程序层。

编写应用程序时,以特定运行时,这意味着应用程序使用并依赖于运行时提供的功能。运行时的选择还决定了应用程序将支持哪些平台。

下表列出了主要的运行时选项:

应用层

CLR/BCL

程序类型

运行在...

ASP.NET

.NET 6

Windows, Linux, macOS

视窗桌面

.NET 6

窗户

视窗 7-10+

毛伊岛(2022年初)

.NET 6

移动设备、桌面版

iOS, 安卓, 苹果操作系统, 视窗 10+

WinUI 3(2022 年初)

.NET 6

赢10

视窗 10+ 桌面

UWP

.NET Core 2.2

Win10 + Win10 设备

Windows 10+ 桌面和设备

(Legacy) .NET Framework

.NET 框架

网页, 视窗

视窗 7-10+

以图形方式显示了此信息,也可作为本书所涵盖内容的指南。

C 语言的运行时#.NET 6

.NET 6 是 Microsoft 的旗舰开源运行时。您可以编写在 Windows、Linux 和 macOS 上运行的 Web 和控制台应用程序,在 Windows 7 到 11 和 macOS 上运行的胖客户端应用程序,以及在 iOS 和 Android 上运行的移动应用程序。本书重点介绍.NET 6 CLR和BCL。

与.NET Framework不同,.NET 6没有预安装在Windows机器上。如果在不存在正确运行时的情况下尝试运行 .NET 6 应用程序,则会出现一条消息,将您定向到可在其中下载运行时的网页。可以通过创建部署来避免这种情况,该部署包括应用程序所需的运行时部分。

注意

.NET 6的前身是.NET 5,其前身是.NET Core 3。(Microsoft从名称中删除了“核心”,并跳过了版本 4)。跳过版本的原因是为了避免与 4.x混淆。

这意味着在大多数情况下,在 .NET Core 版本 1、2 和 3(以及 .NET 5)下编译的程序集将在 .NET 6 下无需修改即可运行。相反,在(任何版本的).NET Framework 下编译的程序集通常与 .NET 6 不兼容。

.NET 6 BCL和CLR与.NET 5(和.NET Core 3)非常相似,其区别主要集中在性能和部署上。

毛 伊 岛

(多平台应用程序 UI,2022 年初)旨在创建适用于 iOS 和 Android 的移动应用程序,以及适用于 macOS 和 Windows 的跨平台桌面应用程序。MAUI 是 Xamarin 的演变,允许单个项目面向多个平台。

UWP 和 WinUI 3

(UWP) 旨在编写在 Windows 10+ 桌面和设备(Xbox、Surface Hub 和 HoloLens)上运行的沉浸式触摸优先应用程序。UWP 应用经过沙盒处理,并通过 Windows 应用商店提供。UWP 预装在 Windows 10 中。它使用 .NET Core 2.2 CLR/BCL 的版本,并且不太可能更新此依赖项。相反,Microsoft发布了一个名为的继任者,作为的一部分。

Windows 应用 SDK 适用于最新的 .NET,与 .NET 桌面 API 更好地集成,并且可以在沙盒外部运行。但是,它尚不支持Xbox或HoloLens等设备。

.NET 框架

是 Microsoft 最初的仅限 Windows 的运行时,用于编写(仅)在 Windows 桌面/服务器上运行的 Web 和富客户端应用程序。没有计划发布重大的新版本,但由于现有应用程序的丰富性,Microsoft将继续支持和维护当前的 4.8 版本。

在.NET Framework中,CLR/BCL与应用程序层集成在一起。用 .NET Framework 编写的应用程序可以在 .NET 6 下重新编译,尽管它们通常需要一些修改。.NET Framework 的某些功能在 .NET 6 中不存在(反之亦然)。

.NET Framework 预装在 Windows 中,并通过 Windows Update 自动修补。当您面向 .NET Framework 4.8 时,可以使用 C# 7.3 及更早版本的功能。

注意

长期以来,“.NET”一词一直被用作包含“.NET”一词的任何技术(.NET Framework,.NET Core,.NET Standard等)的总称。

这意味着Microsoft将.NET Core重命名为.NET造成了不幸的歧义。在本书中,我们将新的.NET称为。为了提到.NET Core及其后续版本,我们将使用短语“.NET Core和.NET 5+”。

更令人困惑的是,.NET(5+)是一个框架,但它与非常不同。因此,在可能的情况下,我们将优先使用术语而不是。

利基运行时

还有以下利基运行时:

.NET Micro Framework 用于在资源高度受限的嵌入式设备(小于 1 MB)上运行 .NET 代码。Unity 是一个游戏开发平台,允许使用 C# 编写游戏逻辑脚本。

也可以在 SQL Server 中运行托管代码。通过 SQL Server CLR 集成,可以使用 C# 编写自定义函数、存储过程和聚合,然后从 SQL 调用它们。它与 .NET Framework 和特殊的“托管”CLR 结合使用,后者强制实施沙箱以保护 SQL Server 进程的完整性。

C语言简史#

以下是每个 C# 版本中新功能的反向时间顺序,以便已经熟悉该语言旧版本的读者受益。

C# 10 中的新增功能

C# 10 随 Visual Studio 2022 一起提供,并在面向 .NET 6 时使用。

文件范围的命名空间

在文件中所有类型都在单个命名空间中定义的常见情况下,C# 10 中的声明可减少混乱并消除不必要的缩进级别:

namespace MyNamespace;  // Applies to everything that follows in the file.class Class1 {}         // inside MyNamespaceclass Class2 {}         // inside MyNamespace
全局使用指令

在 using 指令前面加上 global 关键字时,它会将该指令应用于项目中的所有文件:

global using System;global using System.Collection.Generic;

这样可以避免在每个文件中重复相同的指令。全局 using 指令与 using static .

此外,.NET 6 项目现在支持:如果在项目文件中将 ImplicitUsings 元素设置为 true,则会自动导入最常用的命名空间(基于 SDK 项目类型)。有关更多详细信息,请参阅中的

匿名类型的非破坏性突变

C# 9 引入了 with 关键字,用于对记录执行非破坏性更改。在 C# 10 中,with 关键字也适用于匿名类型:

var a1 = new { A = 1, B = 2, C = 3, D = 4, E = 5 };var a2 = a1 with { E = 10 }; Console.WriteLine (a2);      // { A = 1, B = 2, C = 3, D = 4, E = 10 }
新的解构语法

C# 7 引入了元组(或使用解构方法的任何类型的元组)的解构语法。C# 10 更进一步扩展了此语法,允许您在同一解构中混合赋值和声明:

var point = (3, 4);double x = 0;(x, double y) = point;
结构中的字段初始值设定项和无参数构造函数

从 C# 10 开始,可以在结构中包含字段初始值设定项和无参数构造函数(请参阅中的)。这些仅在显式调用构造函数时执行,因此可以轻松绕过 - 例如,通过默认关键字。引入此功能主要是为了结构记录。

记录结构

记录最初是在 C# 9 中引入的,它们充当编译增强类。在 C# 10 中,记录也可以是结构:

record struct Point (int X, int Y); 

规则在其他方面是相似的:记录结构与具有大致相同的功能(请参阅中的)。 例外情况是,编译器在记录结构上生成的属性是可写的,除非您在记录声明前面加上 readonly 关键字。

Lambda 表达式增强功能

围绕 lambda 表达式的语法已通过多种方式得到增强。首先,允许隐式类型 ( var ):

var greeter = () => "Hello, world";

lambda 表达式的隐式类型是 Action 或 Func 委托,因此在这种情况下,greeter 的类型为 Func<string> 。必须显式声明任何参数类型:

var square = (int x) => x * x;

其次,lambda 表达式可以指定返回类型:

var sqr = int (int x) => x;

这主要是为了提高复杂嵌套 lambda 的编译器性能。

第三,您可以将 lambda 表达式传递到对象、委托或表达式类型的方法参数中:

M1 (() => "test");   // Implicitly typed to Func<string>M2 (() => "test");   // Implicitly typed to Func<string>M3 (() => "test");   // Implicitly typed to Expression<Func<string>>void M1 (object x) {}void M2 (Delegate x) {}void M3 (Expression x) {}

最后,您可以将属性应用于 lambda 表达式的编译生成的目标方法(及其参数和返回值):

Action a = [Description("test")] () => { };

有关更多详细信息,请参阅中的

嵌套属性模式

以下简化语法在 C# 10 中对于嵌套属性模式匹配是合法的(请参阅中的):

var obj = new Uri (";);if (obj is Uri { Scheme.Length: 5 }) ...

这相当于:

if (obj is Uri { Scheme: { Length: 5 }}) ...
调用者参数表达式

应用 [CallerArgumentExpression] 属性的方法参数从调用站点捕获参数表达式:

Print (Math.PI * 2);void Print (double number,           [CallerArgumentExpression("number")] string expr = null)  => Console.WriteLine (expr);// Output: Math.PI * 2

此功能主要用于验证和断言库(请参阅中的)。

其他新功能

#line 指令已在 C# 10 中得到增强,允许指定列和范围。

C# 10 中的内插字符串可以是常量,只要内插的值是常量即可。

记录可以在 C# 10 中密封 ToString() 方法。

C# 的定赋值分析已得到改进,因此表达式如下:

if (foo?.TryParse ("123", out var number) ?? false)  Console.WriteLine (number);

(在 C# 10 之前,编译器将生成错误:“使用未赋值的局部变量'number'。

C# 9.0 中的新增功能

C# 9.0 随 一起提供,并在面向 .NET 5 时使用。

顶级语句

使用(参见中的),您可以编写一个没有 Main 方法和 Program 类包袱的程序:

using System;Console.WriteLine ("Hello, world");

顶级语句可以包含方法(充当本地方法)。您还可以通过 “magic” args 变量访问命令行参数,并向调用方返回一个值。顶级语句后可以跟类型和命名空间声明。

仅初始化设置器

属性声明中的仅(参见中的使用 init 关键字而不是 set 关键字:

class Foo { public int ID { get; init; } }

这类似于只读属性,只是它也可以通过对象初始值设定项进行设置:

var foo = new Foo { ID = 123 };

这使得创建可通过对象初始值设定项而不是构造函数填充的不可变(只读)类型成为可能,并有助于避免接受大量可选参数的构造函数的反模式。仅初始化设置器在中使用时还允许。

记录

(参见中的是一种特殊的类,旨在很好地处理不可变数据。它最特别的特点是关键字(带有):

Point p1 = new Point (2, 3);Point p2 = p1 with { Y = 4 };   // p2 is a copy of p1, but with Y set to 4Console.WriteLine (p2);         // Point { X = 2, Y = 4 }record Point{  public Point (double x, double y) => (X, Y) = (x, y);  public double X { get; init; }  public double Y { get; init; }    }

在简单情况下,记录还可以消除定义属性以及编写构造函数和解构函数的样板代码。我们可以将点记录定义替换为以下内容,而不会丢失功能:

record Point (double X, double Y);

与元组一样,默认情况下,记录表现出结构相等性。记录可以对其他记录进行子类化,并且可以包含类可以包含的相同构造。编译器在运行时将记录实现为类。

模式匹配改进

(参见中的允许 <、>、<= 和 >= 运算符出现在模式中:

string GetWeightCategory (decimal bmi) => bmi switch {  < 18.5m => "underweight",  < 25m => "normal",  < 30m => "overweight",  _ => "obese" };

使用,您可以通过三个新关键字( 和 , or 和 not )组合模式:

bool IsVowel (char c) => c is 'a' or 'e' or 'i' or 'o' or 'u';bool IsLetter (char c) => c is >= 'a' and <= 'z'                            or >= 'A' and <= 'Z';

与 && 和 || 一样运算符,并且具有高于 或 的优先级。您可以使用括号覆盖此设置。

not 组合器可以与类型模式一起使用,以测试对象是否是(不是):

if (obj is not string) ...
目标类型的新表达式

构造对象时,C# 9 允许在编译器可以明确推断类型名称时省略类型名称:

System.Text.StringBuilder sb1 = new();System.Text.StringBuilder sb2 = new ("Test");

当变量声明和初始化位于代码的不同部分时,这特别有用:

class Foo{  System.Text.StringBuilder sb;  public Foo (string initialValue) => sb = new (initialValue);}

在以下情况下:

MyMethod (new ("test"));void MyMethod (System.Text.StringBuilder sb) { ... }

有关详细信息,请参阅中的

互操作改进

C# 9 引入了函数指针(请参阅 章中的“函数指针”和第 中的)。 它们的主要用途是允许非托管代码在 C# 中调用静态方法,而无需委托实例的开销,并且能够在参数和返回类型可 (在每一端以相同方式表示)时绕过 P/Invoke 层。

C# 9 还引入了 nint 和 nuint 本机大小的整数类型(请参阅中的),这些类型在运行时映射到 System.IntPtr 和 System.UIntPtr 。在编译时,它们的行为类似于支持算术运算的数字类型。

其他新功能

此外,C# 9 现在允许你:

重写方法或只读属性,使其返回派生类型(请参阅中的)将属性应用于本地函数(请参阅中的)将 static 关键字应用于 lambda 表达式或局部函数,以确保不会意外捕获局部变量或实例变量(请参阅中的)通过编写 GetEnumerator 扩展方法,使任何类型都使用 foreach 语句定义在首次加载程序集时执行一次的方法,方法是将 [ModuleInitializer] 属性应用于(静态无参数)方法使用“丢弃”(下划线符号)作为 lambda 表达式参数编写必须实现的方法 - 启用 Roslyn 的新等方案(请参阅中的)将属性应用于方法、类型或模块,以防止运行时初始化局部变量(请参阅中的)C# 8.0 中的新增功能

C# 8.0 首次随 一起提供,今天在面向 .NET Core 3 或 .NET Standard 2.1 时仍在使用。

指数和范围

简化了数组的元素或部分(或低级类型 Span<T> 和 ReadOnlySpan<T> )的使用。

索引允许您使用 ^ 运算符引用相对于数组的元素。^1 表示最后一个元素,^2 表示倒数第二个元素,依此类推:

char[] vowels = new char[] {'a','e','i','o','u'};char lastElement  = vowels [^1];   // 'u'char secondToLast = vowels [^2];   // 'o'

范围允许您使用 ..算子:

char[] firstTwo =  vowels [..2];    // 'a', 'e'char[] lastThree = vowels [2..];    // 'i', 'o', 'u'char[] middleOne = vowels [2..3]    // 'i'char[] lastTwo =   vowels [^2..];   // 'o', 'u'

C# 借助索引和范围类型实现索引和范围:

Index last = ^1;Range firstTwoRange = 0..2;char[] firstTwo = vowels [firstTwoRange];   // 'a', 'e'

可以通过定义参数类型为 Index 或 Range 的索引器来支持自己的类中的索引和范围:

class Sentence{  string[] words = "The quick brown fox".Split();  public string this   [Index index] => words [index];  public string[] this [Range range] => words [range];}

有关更多信息,请参阅中的

零合并分配

这??= 运算符仅在变量为 null 时才分配变量。而不是

if (s == null) s = "Hello, world";

您现在可以这样写:

s ??= "Hello, world";
使用声明

如果省略 using 语句后面的方括号和语句块,则它将成为 。然后,当执行落在语句块之外时,将释放资源:

if (File.Exists ("file.txt")){  using var reader = File.OpenText ("file.txt");  Console.WriteLine (reader.ReadLine());  ...}

在这种情况下,当执行落在 if 语句块之外时,将释放读取器。

只读成员

C# 8 允许您将只读修饰符应用于结构的函数,确保如果尝试修改任何字段,则会生成编译时错误:

struct Point{  public int X, Y;  public readonly void ResetX() => X = 0;  // Error!} 

如果只读函数调用非只读函数,编译器将生成警告(并防御性地复制结构以避免发生突变的可能性)。

静态本地方法

将 static 修饰符添加到局部方法可防止它看到封闭方法的局部变量和参数。这有助于减少耦合,并使本地方法能够随心所欲地声明变量,而不会有与包含方法中的变量发生冲突的风险。

默认接口成员

C# 8 允许您向接口成员添加默认实现,使其成为可选的实现:

interface ILogger{  void Log (string text) => Console.WriteLine (text);}

这意味着您可以在不中断实现的情况下向接口添加成员。默认实现必须通过接口显式调用:

((ILogger)new Logger()).Log ("message");

接口还可以定义静态成员(包括字段),这些成员可以从默认实现中的代码访问:

interface ILogger{  void Log (string text) => Console.WriteLine (Prefix + text);  static string Prefix = ""; }

或从接口外部,除非通过静态接口成员(例如私有、受保护或内部)上的可访问性修饰符进行限制:

ILogger.Prefix = "File log: ";

禁止使用实例字段。有关更多详细信息,请参阅中的

切换表达式

从 C# 8 开始,可以在的上下文中使用 switch:

string cardName = cardNumber switch    // assuming cardNumber is an int{  13 => "King",  12 => "Queen",  11 => "Jack",  _ => "Pip card"   // equivalent to 'default'};

有关更多示例,请参阅中的

元组、位置和属性模式

C# 8 支持三种新模式,主要是为了 switch 语句/表达式(请参阅中的)。允许您打开多个值:

int cardNumber = 12; string suite = "spades";string cardName = (cardNumber, suite) switch{  (13, "spades") => "King of spades",  (13, "clubs") => "King of clubs",  ...};

模式允许对公开解构函数的对象使用类似的语法,允许您匹配对象的属性。您可以在开关中和 is 运算符中使用所有模式。下面的示例使用来测试 obj 是否为长度为 4 的字符串:

if (obj is string { Length:4 }) ...
可为空的引用类型

可为 null 类型为值类型带来可为空性,而 的引用类型则相反,为引用类型带来(一定程度的),目的是帮助避免 NullReferenceExceptions。可为 null 的引用类型引入了一种安全级别,当编译器检测到存在生成 NullReferenceException 风险的代码时,该级别纯粹由编译器以警告或错误的形式强制实施。

可以在项目级别(通过 项目文件中的 Nullable 元素)或在代码(通过 #nullable 指令)启用可为 null 的引用类型。启用后,编译器会将非可为空性设为默认值:如果希望引用类型接受 null,则必须应用 ?指示后缀:

#nullable enable    // Enable nullable reference types from this point onstring s1 = null;   // Generates a compiler warning! (s1 is non-nullable)string? s2 = null;  // OK: s2 is nullable reference type

未初始化的字段也会生成警告(如果类型未标记为可为空),如果编译器认为可能发生 NullReferenceException,则取消引用可为空的引用类型也是如此:

void Foo (string? s) => Console.Write (s.Length);  // Warning (.Length)

若要删除警告,可以使用 ( !

void Foo (string? s) => Console.Write (s!.Length);

有关完整讨论,请参阅中的

异步流

在 C# 8 之前,可以使用 yield return 编写,或等待编写。但是你不能同时做这两件事,并编写一个等待的迭代器,异步生成元素。C# 8 通过引入来解决此问题:

async IAsyncEnumerable<int> RangeAsync (  int start, int count, int delay){  for (int i = start; i < start + count; i++)  {    await Task.Delay (delay);    yield return i;  }}

await foreach 语句使用异步流:

await foreach (var number in RangeAsync (0, 10, 100))  Console.WriteLine (number);

有关详细信息,请参阅中的

C# 7.x 中的新增功能

C# 7.x 最初随 Visual Studio 2017 一起提供。今天,当您面向.NET Core 7,.NET Framework 3.2019至2.4或.NET Standard 6.4时,Visual Studio 8仍在使用C# 2.0。

C# 7.3

C# 7.3 对现有功能进行了细微改进,例如允许将相等运算符与元组一起使用、改进重载分辨率以及将特性应用于自动属性的支持字段的功能:

[field:NonSerialized]public int MyProperty { get; set; }

C# 7.3 还基于 C# 7.2 的高级低分配编程功能构建,能够重新分配 ,在索引固定字段时无需固定,并且使用 stackalloc 支持字段初始值设定项:

int* pointer  = stackalloc int[] {1, 2, 3};Span<int> arr = stackalloc []    {1, 2, 3};

请注意,堆栈分配的内存可以直接分配给 Span<T> 。我们将在第 中描述跨度,以及为什么要使用它们。

C# 7.2

C# 7.2 添加了一个新的私有受保护修饰符(内部修饰符和受保护修饰符的),在调用方法时跟随带有位置参数的命名参数的功能,以及只读结构。只读结构强制所有字段都是只读的,以帮助声明意图并允许编译器更大的优化自由度:

readonly struct Point{  public readonly int X, Y;   // X and Y must be readonly}

C# 7.2 还添加了专门的功能来帮助进行微优化和低分配编程:请参阅 章中的“引用”和“,以及中的

C# 7.1

从 C# 7.1 开始,如果可以推断出类型,则可以在使用默认关键字时省略该类型:

decimal number = default;   // number is decimal

C# 7.1 还放宽了 switch 语句的规则(以便可以在泛型类型参数上进行模式匹配),允许程序的 Main 方法是异步的,并允许推断元组元素名称:

var now = DateTime.Now;var tuple = (now.Hour, now.Minute, now.Second);
数字文字改进

C# 7 中的数字文本可以包含下划线以提高可读性。这些称为数字分隔符,编译器会忽略这些:

int million = 1_000_000;

可以使用 0b 前缀指定:

var b = 0b1010_1011_1100_1101_1110_1111;
输出变量和丢弃

C# 7 使调用包含 out 参数的方法变得更加容易。首先,您现在可以动态声明(请参阅中的):

bool successful = int.TryParse ("123", out int result);Console.WriteLine (result);

当调用具有多个 out 参数的方法时划线字符丢弃您不感兴趣的方法:

SomeBigMethod (out _, out _, out _, out int x, out _, out _, out _);Console.WriteLine (x);
类型模式和模式变量

您还可以使用 is 运算符动态引入变量。这些被称为模式变量(参见中的):

void Foo (object x){  if (x is string s)    Console.WriteLine (s.Length);}

switch 语句还支持类型模式,因此您可以打开常量(请参阅中的)。您可以使用 when 子句指定条件,也可以打开空值:

switch (x){  case int i:    Console.WriteLine ("It's an int!");    break;  case string s:    Console.WriteLine (s.Length);    // We can use the s variable    break;  case bool b when b == true:        // Matches only when b is true    Console.WriteLine ("True");    break;  case null:    Console.WriteLine ("Nothing");    break;}
本地方法

在另一个函数中声明的方法(请参阅中的):

void WriteCubes(){  Console.WriteLine (Cube (3));  Console.WriteLine (Cube (4));  Console.WriteLine (Cube (5));  int Cube (int value) => value * value * value;}

局部方法仅对包含函数可见,并且可以像 lambda 表达式一样捕获局部变量。

更多善于表达的成员

C# 6 为方法、只读属性、运算符和索引器引入了表达式体“fat-arrow”语法。C# 7 将其扩展到构造函数、读/写属性和终结器:

public class Person{  string name;  public Person (string name) => Name = name;  public string Name  {    get => name;    set => name = value ?? "";  }  ~Person () => Console.WriteLine ("finalize");}
解构函数

C# 7 引入了解构模式(请参阅中的)。构造函数通常采用一组值(作为参数)并将它们分配给字段,而则执行相反的操作,并将字段分配回一组变量。我们可以在前面的示例中为 Person 类编写一个解构函数,如下所示(除了异常处理):

public void Deconstruct (out string firstName, out string lastName){  int spacePos = name.IndexOf (' ');  firstName = name.Substring (0, spacePos);  lastName = name.Substring (spacePos + 1);}

解构函数使用以下特殊语法调用:

var joe = new Person ("Joe Bloggs");var (first, last) = joe;          // DeconstructionConsole.WriteLine (first);        // JoeConsole.WriteLine (last);         // Bloggs
元组

也许对 C# 7 最显着的改进是显式支持(请参阅中的)。元组提供了一种存储一组相关值的简单方法:

var bob = ("Bob", 23);Console.WriteLine (bob.Item1);   // BobConsole.WriteLine (bob.Item2);   // 23

C# 的新元组是使用 System.ValueTuple<...>泛型结构。但是多亏了编译器的魔力,元组元素可以命名为:

var tuple = (name:"Bob", age:23);Console.WriteLine (tuple.name);     // BobConsole.WriteLine (tuple.age);      // 23

使用元组,函数可以返回多个值,而无需求助于 out 参数或额外的类型包袱:

static (int row, int column) GetFilePosition() => (3, 10);static void Main(){  var pos = GetFilePosition();  Console.WriteLine (pos.row);      // 3  Console.WriteLine (pos.column);   // 10}

元组隐式支持解构模式,因此您可以轻松地将它们为单个变量:

static void Main(){  (int row, int column) = GetFilePosition();   // Creates 2 local variables  Console.WriteLine (row);      // 3   Console.WriteLine (column);   // 10}
抛出表达式

在 C# 7 之前,throw 始终是一个语句。现在,它也可以在表达式体函数中显示为表达式:

public string Foo() => throw new NotImplementedException();

抛出表达式也可以出现在三元条件表达式中:

string Capitalize (string value) =>  value == null ? throw new ArgumentException ("value") :  value == "" ? "" :  char.ToUpper (value[0]) + value.Substring (1);
C# 6.0 中的新增功能

附带的C# 0.2015具有新一代编译器,完全用C#编写。新的编译器称为项目“Roslyn”,通过库公开整个编译管道,允许您对任意源代码执行代码分析。编译器本身是开源的,源代码可在 获得。

此外,C# 6.0 还具有几个次要但重要的增强功能,主要旨在减少代码混乱。

(“Elvis”)运算符(参见中的避免了在调用方法或访问类型成员之前显式检查null。在下面的示例中,结果计算结果为 null,而不是抛出 NullReferenceException:

System.Text.StringBuilder sb = null;string result = sb?.ToString();      // result is null

(参见中的允许以 lambda 表达式的样式更简洁地编写组成单个表达式的方法、属性、运算符和索引器:

public int TimesTwo (int x) => x * 2;public string SomeProperty => "Property value";

()允许您为自动属性分配初始值:

public DateTime TimeCreated { get; set; } = DateTime.Now;

初始化的属性也可以是只读的:

public DateTime TimeCreated { get; } = DateTime.Now;

还可以在构造函数中设置只读属性,从而更轻松地创建不可变(只读)类型。

()允许公开索引器的任何类型的单步初始化:

var dict = new Dictionary<int,string>(){  [3] = "three",  [10] = "ten"};

字符串(参见中的的简洁替代方法。格式:

string s = $"It is {DateTime.Now.DayOfWeek} today";

(参见中的允许您将条件应用于 catch 块:

string html;try{  html = await new HttpClient().GetStringAsync (";);}catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout){  ...}

using static(参见中的指令允许您导入类型的所有静态成员,以便您可以使用这些非限定成员:

using static System.Console;...WriteLine ("Hello, world");  // WriteLine instead of Console.WriteLine

nameof()运算符以字符串形式返回变量、类型或其他符号的名称。这样可以避免在 Visual Studio 中重命名符号时中断代码:

int capacity = 123;string x = nameof (capacity);   // x is "capacity"string y = nameof (Uri.Host);   // y is "Host"

最后,您现在可以等待内部捕获并最终阻止。

C# 5.0 中的新增功能

C# 5.0 的一大新功能是通过两个新关键字 async 和 await 支持。异步函数支持,这使得编写响应式和线程安全的胖客户端应用程序变得更加容易。它们还可以轻松编写高并发且高效的 I/O 绑定应用程序,这些应用程序不会占用每个操作的线程资源。我们将在第 中详细介绍异步函数。

C# 4.0 中的新增功能

C# 4.0 引入了四个主要增强功能:

( 章和第 19 章)将(解析类型和成员的过程)从编译时推迟到运行时,并且在需要复杂反射代码的方案中非常有用。动态绑定在与动态语言和 COM 组件进行互操作时也很有用。

()允许函数指定默认参数值,以便调用方可以省略参数,参数允许函数调用方按名称而不是位置标识参数。

在 C# 4.0(和第 )中放宽了类型规则,以便泛型接口和泛型委托中的类型参数可以标记为变或,从而允许更自然的类型转换。

()在 C# 4.0 中以三种方式得到增强。首先,参数可以在没有 ref 关键字的情况下通过引用传递(与可选参数结合使用特别有用)。其次,可以而不是包含 COM 互操作类型的程序集。链接互操作类型支持类型等效性,避免了的需求,并结束了版本控制和部署难题。第三,从链接互操作类型返回 COM-Variant 类型的函数被映射到动态而不是对象,从而消除了强制转换的需要。

C# 3.0 中的新增功能

添加到 C# 3.0 的功能主要集中在 (LINQ) 功能上。LINQ 允许直接在 C# 程序中编写查询并检查其正确性,并查询本地集合(如列表或 XML 文档)或远程数据源(如数据库)。为支持 LINQ 而添加的 C# 3.0 功能包括隐式类型化局部变量、匿名类型、对象初始值设定项、lambda 表达式、扩展方法、查询表达式和表达式树。

变量(var关键字,)允许您在声明语句中省略变量类型,从而允许编译器推断它。这减少了混乱,并允许(),匿名类型是动态创建的简单类,通常用于 LINQ 查询的最终输出。您还可以隐式类型化数组()。

()允许您在构造函数调用后以内联方式设置属性,从而简化了对象构造。对象初始值设定项同时适用于命名类型和匿名类型。

()是由编译器动态创建的微型函数;它们在“流畅”的 LINQ 查询中特别有用()。

方法()使用新方法扩展现有类型(不更改类型的定义),使静态方法感觉像实例方法。LINQ 的查询运算符作为扩展方法实现。

()为编写 LINQ 查询提供了更高级别的语法,在处理多个序列或范围变量时,该语法可以简单得多。

()是微型代码文档对象模型 (DOM),用于描述分配给特殊类型 Expression<TDelegate> 的 lambda 表达式。表达式树使 LINQ 查询可以远程执行(例如,在数据库服务器上),因为它们可以在运行时进行内省和转换(例如,转换为 SQL 语句)。

C# 3.0 还添加了自动属性和分部方法。

()通过让编译器自动执行该工作来减少编写属性的工作,这些属性只是获取/设置私有支持字段。()让自动生成的分部类为手动创作提供可自定义的钩子,如果不使用,这些钩子就会“消失”。

C# 2.0 中的新增功能

C# 2 中的重大新功能是泛型(第 章)、可为空的值类型( 章)、迭代器()和匿名方法(lambda 表达式的前身)。这些功能为在 C# 3 中引入 LINQ 铺平了道路。

C# 2 还添加了对分部类、静态类以及大量次要和杂项功能(如命名空间别名限定符、友元程序集和固定大小缓冲区)的支持。

泛型的引入需要新的 CLR (CLR 2.0),因为泛型在运行时保持完整类型保真度。

标签: #net开发触屏版软件 #js运算符中和的区别在哪里 #winccnet变量 #netnonserialized