.NET开发:语法笔记
最后更新时间:
文章总字数:
预计阅读时间:
摘要
本文为自用笔记,包含开发过程中常用的C#与其它语言常用语法、查漏补缺等内容。
知识点内容根据各个教程收集整理。
所有教程版权归各自作者所有。
C#语法&杂项查漏补缺
关键字
init
只能在构造函数中赋值
1 | |
Lock
lock 关键字
虽然 sleep 方法可以控制线程的暂停时间,从而改变多个线程之间的顺序,然而每个线程的执行顺序是随机且不确定的。 线程同步是通过共享线程资源来实现的,允许控制一次执行一个线程并交替执行每个线程。 为了在c#语言中实现线程同步,可以使用lock关键字、monitor类和mutex类。 同步线程最简单的方法之一是使用lock语句,它确保被锁定的线程在完成执行之前不能执行任何其他线程。 lock 的语法形式如下所示。
1 | |
lock 后面通常是一个 Object 类型的值,也可以使用 this 关键字来表示。 最好是在 lock 中使用私有的只读的非静态变量或私有的静态成员变量,即使用 Private 或 Private static 修饰的成员。 例如:
1 | |
sealed
应用于某个类时,sealed 修饰符可阻止其他类继承自该类。
类似于Java中的final
在下面的示例中,类 B 继承自类 A,但没有类可以继承自类 B。
1 | |
还可以对替代基类中的虚方法或属性的方法或属性使用 sealed 修饰符。 这可以允许类派生自你的类并防止它们替代特定虚方法或属性。
在下面的示例中,Z 继承自 Y,但 Z 无法替代在 X 中声明并在 Y 中密封的虚函数 F。
1 | |
在类中定义新方法或属性时,可以通过不将它们声明为虚拟,来防止派生类替代它们。
将 abstract 修饰符与密封类结合使用是错误的,因为抽象类必须由提供抽象方法或属性的实现的类来继承。
应用于方法或属性时,sealed 修饰符必须始终与 override 结合使用。
因为结构是隐式密封的,所以无法继承它们。
来源详见sealed(C# 参考)
readonly
在字段声明中,readonly 指示只能在声明期间或在同一个类的构造函数中向字段赋值。 可以在字段声明和构造函数中多次分配和重新分配只读字段。
构造函数退出后,不能分配 readonly 字段。 此规则对于值类型和引用类型具有不同的含义:
- 由于值类型直接包含数据,因此属于
readonly值类型的字段不可变。 - 由于引用类型包含对其数据的引用,因此属于
readonly引用类型的字段必须始终引用同一对象。该对象是可变的。readonly修饰符可防止字段替换为引用类型的其他实例。 但是,修饰符不会阻止通过只读字段修改字段的实例数据。
示例
即使在类构造函数中给字段 year 赋了值,也无法在方法 ChangeYear 中更改其值:
1 | |
const
readonly 关键字不同于 const 关键字。 const 字段只能在该字段的声明中初始化。 可以在字段声明和任何构造函数中多次分配 readonly 字段。 因此,根据所使用的构造函数,readonly 字段可能具有不同的值。 另外,虽然 const 字段是编译时常量,但 readonly 字段可用于运行时常量,如下面的示例所示:
1 | |
来源详见readonly(C# 参考)
ref与in
C#中out和ref之间的区别:
首先:两者都是按地址传递的,使用后都将改变原来参数的数值。
其次:ref可以把参数的数值传递进函数,但是out是要把参数清空,就是说你无法把一个数值从out传递进去的,out进去后,参数的数值为空,所以你必须初始化一次。这个就是两个的区别,或者说就像有的网友说的,ref是有进有出,out是只出不进。
ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字
来源详见关于C#中的ref的使用
属性
CallerMemberName属性
记录调用信息.
CallerMemberName:调用者名称。在构造函数,析构函数、属性等特殊的地方调用CallerMemberName属性所标记的函数时,获取的值有所不同。
| 调用的地方 | CallerMemberName获取的结果 |
|---|---|
| 方法、属性或事件 | 方法,属性或事件的名称 |
| 构造函数 | 字符串 “.ctor” |
| 静态构造函数 | 字符串 “.cctor” |
| 析构函数 | 字符串 “Finalize” |
| 用户定义的运算符或转换 | 生成的名称成员,例如, “op_Addition” |
| 特性构造函数 | 特性所应用的成员的名称 |
示例:
调用者:
1 | |
被调用者:
1 | |
输出结果:
1 | |
常用方法备查
int.TryParse
C#中针对转换有一个TryParse的方法。
如果转换成功则返回true。否则返回false。
1 | |
s是要转换的字符串,i 是转换的结果。
ConcurrentBag
ConcurrentBag<T>:表示对象的线程安全的无序集合。
包对于在排序无关紧要时存储对象很有用,与集不同,包支持重复项。 ConcurrentBag<T> 是线程安全包实现,针对同一线程将同时生成和使用存储在包中的数据的方案进行优化。
ConcurrentBag<T> 接受 null 为引用类型的有效值。
其它
_
在部分名称规范中,以下划线_开头表示private
可空引用类型检查
- 在csproj中开启可空引用类型检查
1 | |
在引用类型后添加”?”代表可空。否则编译器在识别到有可能为Null时会发出警告
在后面添加”!”抑制Null警告
Parallel 类
提供对并行循环和区域的支持
示例
1 | |
程序集
.NET 中的程序集
程序集构成了 .NET 应用程序的部署、版本控制、重用、激活范围和安全权限的基本单元。 程序集是为协同工作而生成的类型和资源的集合,这些类型和资源构成了一个逻辑功能单元。 程序集采用可执行文件 (.exe) 或动态链接库文件 (.dll) 的形式,是 .NET 应用程序的构建基块 。 它们向公共语言运行时提供了注意类型实现代码所需的信息。
在 .NET 和 .NET Framework 中,可从一个或多个源代码文件生成程序集。 在 .NET Framework 中,程序集可以包含一个或多个模块。 因此,大型项目可以采用以下规划:由多个开发者单独开发各源代码文件或模块,最后整合所有这些内容以创建一个程序集。
来源详见.NET 中的程序集
NuGet
简介
NuGet 是免费、开源的包管理开发工具,专注于在 .NET 应用开发过程中,简单地合并第三方的组件库。
当需要分享开发的工具或是库,需要建立一个Nuget package,然后把这个package放到Nuget的站点。如果想要使用别人已经开发好的工具或是库,只需要从站点获得这个package,并且安装到自己的Visual Studio项目或是解决方案里。
NuGet能更方便地把一些dll和文件(如jquery)添加到项目中,而不需要从文件中复制拷贝。
使用
VS可视化使用

以Newtonsoft.Json为例:
- 选中Newtonsoft.Json,在右侧就会出现Newtonsoft.Json的详细信息
- 选中要将Newtonsoft.Json下载到的项目,然后点击安装。NuGet会在解决方案目录下创建packages目录并将包下载到该目录
- 下载完成后VS会自动将Newtonsoft.Json添加到我们的引用。
使用控制台
打开NuGet控制台管理器

- 通过
Install-Package“包名” 这个命令就能完成对目标包的引用。 - 通过
get-help NuGet来获取其他命令的信息 Get-Package获取当前已经安装的包UnInstall-Package卸载某个包Update-Package升级某个包
文档
更多使用问题参考官方文档
异步编程
async \ await 简化多线程程序开发,不等于多线程
基本使用
异步方法:使用 async 关键字修饰的方法
- 异步方法返回值一般为
Task<T>,异步方法名惯例以Async结尾。 - 异步方法可以没有返回值。但即时没有返回值也应返回非泛型的Task
- 调用异步方法一般使用
await,直接拿到泛型位置指定的类型 - 异步方法具有传染性,若在方法中调用
await, 则其自身需要使用async进行修饰
异步方法代码参考:
1 | |
同步版本:
1 | |
- 异步代码使用
Task<T>和Task,它们是对后台所完成的工作进行建模的结构。 async关键字将方法转换为异步方法,这使你能在其正文中使用await关键字。- 应用
await关键字后,它将挂起调用方法,并将控制权返还给调用方,直到等待的任务完成。 - 仅允许在异步方法中使用
await。
此部分来源参考异步编程 - C#
编写异步方法
使用 Async 关键字,可直接返回值
无返回值示例
1 | |
有返回值示例
1 | |
调用示例
1 | |
using
using关键字有两个主要的用途:
using语句定义了一个作用域,在作用域的尾部将会释放对象using指令为命名空间创建别名或导入其他命名空间中定义的类型
指令
using指令允许使用在命名空间中定义的类型,而无需指定该类型的全名(与C系语言中 include 同)。 using 指令会导入命名空间中的所有类型
1 | |
using指令可以添加修饰符:
global:该修饰符与向项目中每个源文件添加相同的using指令具有相同的效果。该修饰符是在C# 10中引入的。static:该修饰符从单个类型导入静态成员和嵌套类型,而不是导入所有类型。它是C#6.0中引入的。
可以结合使用这两个修饰符,来达到从项目所有源文件中的类型中导入静态成员的目的。
还可以使用using alias directive(别名指令)为命名空间或类型创建别名。示例如下
1 | |
语句
IDisposable从命名来看就是个接口。
Disposable:可任意使用的、一次性的、用完即丢弃的。
该接口的定义是,提供了一种释放非托管资源的机制。
IDisposable的文档部分非常直白地介绍了该接口的用途:
此接口的主要用途是释放非托管资源。
当管理的对象不再使用时,GC(.NET平台的垃圾回收器)会自动释放分配给该对象的内存。但是呢,到底何时回收垃圾是无法被预测的。而且,GC也不了解非托管资源,如窗口句柄、或打开的文件和流。
使用此接口的Dispose方法可以用GC显式地释放非托管的资源。当某个对象不再被需要时,对象的使用者可以调用此方法。
那么使用实现了IDisposable接口的对象通常有两种方式,
其中一种就是使用一种语言结构,如C#和VB中的using语句,以及F#中的use语句或using函数。当环境支持该语言结构时,你可以使用它来代替显式地调用IDisposable.Dispose。
using(){}:
1 | |
using声明,代码在离开作用域之后会自动释放资源(C# 8加入):
1 | |
此部分来源详见C#中的using关键字
IDisposable
C#提供了方便的垃圾回收机制,使我们几乎不再需要为资源管理费心。可事实上,能被垃圾回收释放掉的只是托管资源,非托管资源还是需要我们手动释放。而为了实现这一目的,C#提供了 IDisposable 接口.
此部分来源详见说说IDisposable
托管资源和非托管资源
- 托管资源:托管资源是指由垃圾收集器(GC)管理的内存。
- 非托管资源:非托管资源有文件句柄(类似于文件描述符),网络连接,数据库连接等等。
非托管资源的释放主要有:
析构函数:与创建资源的构造函数相对应,析构函数可以销毁对象,一般在被垃圾回收器销毁对象之前调用。但C#中析构函数与C++的不同,它不是立即执行。因为要等待GC回收,所以无法确定析构函数的执行时间,这样的不可控在代码中是不能接受的。与此同时,它会延时对象删除的时间,跟没有析构函数的对象相比,它需要调用才能删除。频繁使用析构函数,对性能的影响会变得非常显著。
实现
System.IDisposable:实现IDisposable是C#中推荐的方式,它需要实现一个dispose()方法,以此来显示的释放资源,这样避免了不确定的调用时间。在需要释放资源时,直接调用即可。从规范而言建议在finally中,释放避免异常之后资源不释放的情况。using语句:using语句不仅可以用于引用namespace,还能在代码中使用。它相当方便,与上面实现IDisposable方法等效。他帮我们实现了IDisposable的方法,并且在代码块的末尾调用Dispose方法。
此部分来源详见托管资源与非托管资源
async背后的线程切换
await调用的等待期间,.NET会将当前线程返回给线程池。待异步方法执行完毕之后另取一个线程执行后面的代码.
async缺点:
- 异步方法会生成一个类,运行效率没有普通方法高
- 可能会占用非常多的线程
不适用sleep()
采用非阻止方式编写等待任务的代码
通过阻止当前线程来等待 Task 完成的方法可能导致死锁和已阻止的上下文线程,且可能需要更复杂的错误处理方法。 下表提供了关于如何以非阻止方式处理等待任务的指南:
| 使用以下方式… | 而不是… | 若要执行此操作… |
|---|---|---|
await |
Task.Wait 或 Task.Result |
检索后台任务的结果 |
await Task.WhenAny |
Task.WaitAny |
等待任何任务完成 |
await Task.WhenAll |
Task.WaitAll |
等待所有任务完成 |
await Task.Delay |
Thread.Sleep |
等待一段时间 |
CancellationToken结构体
属性
| 名称 | 介绍 |
|---|---|
CanBeCanceled |
获取此标记是否能处于已取消状态 |
IsCancellationRequested |
获取是否已请求取消此标记 |
None |
返回一个空 CancellationToken 值 |
WaitHandle |
获取在取消标记时收到信号的 WaitHandle |
此部分来源详见CancellationToken 结构
Task类
Task类的重要方法
| 名称 | 介绍 |
|---|---|
WhenAll(IEnumerable<Task>) |
创建一个任务,该任务将在可枚举集合中的所有 Task 对象都已完成时完成。 |
WhenAll(Task[]) |
创建一个任务,该任务将在数组中的所有 Task 对象都已完成时完成。 |
WhenAll<TResult>(IEnumerable<Task<TResult>>) |
创建一个任务,该任务将在可枚举集合中的所有Task<TResult>对象都已完成时完成。 |
WhenAll<TResult>(Task<TResult>[]) |
创建一个任务,该任务将在数组中的所有 Task<TResult> 对象都已完成时完成。 |
WhenAny(IEnumerable<Task>) |
任何提供的任务已完成时,创建将完成的任务。 |
WhenAny(Task, Task) |
提供的任一任务完成时,创建将完成的任务。 |
WhenAny(Task[]) |
任何提供的任务已完成时,创建将完成的任务。 |
WhenAny<TResult>(IEnumerable<Task<TResult>>) |
任何提供的任务已完成时,创建将完成的任务。 |
WhenAny<TResult>(Task<TResult>, Task<TResult>) |
提供的任一任务完成时,创建将完成的任务。 |
WhenAny<TResult>(Task<TResult>[]) |
任何提供的任务已完成时,创建将完成的任务。 |
FromResult<TResult>(TResult) |
创建指定结果的、成功完成的 Task<TResult>。 |
Linq
语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。
委托
C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。
委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。
- 委托是可以指向方法的类型,调用委托变量是执行的就是所指向的方法。
- .NET中定义了有返回值(Func)和无返回值(Action)的泛型委托。一般不用自定义类型。
声明委托
声明委托的语法如下:
1 | |
以单一String类型参数和int类型返回值举例:
1 | |
委托的声明、实例化和使用举例:
1 | |
此部分来源详见C# 委托
Lambda
“Lambda表达式”是一个匿名函数,是一种高效的类似于函数式编程的表达式,Lambda简化了开发中需要编写的代码量。它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型,支持带有可绑定到委托或表达式树的输入参数的内联表达式。
所有Lambda表达式都使用Lambda运算符=>,该运算符读作”goes to”。Lambda运算符的左边是输入参数(如果有),右边是表达式或语句块。Lambda表达式x => x * x读作”x goes to x times x”。形如(参数)=>{操作}。
LINQ
下文介绍方法语法
Where:遍历集合中每一个元素,对于每一个元素使用表达式判断是否为True,为True将结果放回到返回集合中
LINQ中常用var推断类型
1 | |
Count:返回满足条件的元素个数
1 | |
Any:是否至少有一条数据
1 | |
Single:有且只有一条满足要求的数据。没有或者多于一条都会报错
1 | |
SingleOrDefault:一个或默认值;最多一个符合要求,不满足则返回默认值
1 | |
First:返回第一条,至少有一条
1 | |
FirstOrDefault:第一个或默认值
排序:
OrdeBy:正序排序
OrderByDescending:降序排序
1 | |
多规则排序:
ThenBy:再根据新的条件正序排序
ThenByDescending:再根据新的条件降序排序
1 | |
限制结果集:
Skip:跳过n条数据
Take:获取最多n条数据,不够有多少取多少
1 | |
聚合函数:
Max:取最大值
1 | |
Min:取最小值
Average:取平均值
1 | |
Sum:求和
Count:计数
分组:
GroupBy:参数为分组条件表达式
1 | |
投影:
将集合中的每个元素逐个转换为另一种类型
Select:
1 | |
匿名类:
1 | |
转换:
ToArray/ToList
查询语法
一个等价的查询语法和方法语法的实例:
1 | |