Csharp
1 .NET
笔记来自官方文档(非常重要)和视频
1.1 .NET简介
.NET(.NET官方文档) 是一种用于构建多种应用的免费开源开发平台,例如:
- Web 应用、Web API 和微服务
- 云中的无服务器函数
- 云原生应用
- 移动应用
- 桌面应用
- Windows WPF
- Windows 窗体
- 通用 Windows 平台 (UWP)
- 游戏
- 物联网 (IoT)
- 机器学习
- 控制台应用
- Windows 服务
- 使用类库在不同应用和应用类型中共享功能。
使用 .NET 时,无论你正在构建哪种类型的应用,代码和项目文件看起来都一样。 可以访问每个应用的相同运行时、API 和语言功能。
跨平台(多操作系统创建,多处理器架构) **开源 ** .NET 支持三种编程语言:C#,F#和Visual Basic
.NET SDK 是一组用于开发和运行 .NET 应用程序的库和工具。
1.2 SDK 和运行时
.NET SDK 是一组用于开发和运行 .NET 应用程序的库和工具。
SDK 下载包括以下组件:
- .NET CLI 可用于本地开发和持续集成脚本的命令行工具;
dotnet
驱动程序 用于运行依赖于框架的应用的 CLI 命令;- Roslyn 和 F# 编程语言编译器;
- MSBuild 生成引擎
- .NET 运行时。 提供类型系统、程序集加载、垃圾回收器、本机互操作和其他基本服务;
- 运行时库。 提供基元数据类型和基本实用程序;
- ASP.NET Core 运行时。 为连接 Internet 的应用(如 Web 应用、IoT 应用和移动后端)提供基本服务;
- 桌面运行时。 为 Windows 桌面应用(包括 Windows 窗体和 WPF)提供基本服务;
运行时下载包括以下组件:
- (可选)桌面或 ASP.NET Core 运;
- .NET 运行时。 提供类型系统、程序集加载、垃圾回收器、本机互操作和其他基本服务;
- 运行时库。 提供基元数据类型和基本实用程序;
dotnet
驱动程序。 用于运行依赖于框架的应用的 CLI 命令;
1.3 NuGet
NuGet 是为 .NET 设计的开源包管理器。 NuGet 包是具有 .nupkg
扩展的 .zip 文件,此扩展包含编译代码 (DLL)、与该代码相关的其他文件以及描述性清单(包含包版本号等信息)。 使用代码的开发人员共享创建包,并将其发布到 nuget.org 或专用主机。 希望使用共享代码的开发人员将包添加到其项目中,然后可以在项目代码中调用包公开的 API。
有关详细信息,请参阅 NuGet 文档。
1.4 其他关于.NET的概念
…
2 C# 语言入门
2.1 简介
2.1.1 语言介绍
C#(”See Sharp”)是一种新式编程语言,不仅面向对象,还类型安全。 开发人员利用 C# 能够生成在 .NET 中运行的多种安全可靠的应用程序。
C# 是面向对象的、面向组件的编程语言。 C# 提供了语言构造来直接支持这些概念,让 C# 成为一种非常自然的语言,可用于创建和使用软件组件。 自诞生之日起,C# 就添加了支持新工作负载和新兴软件设计实践的功能。 C# 本质上是面向对象的语言。_* 你需要定义类型及其行为。
多项 C# 功能有助于创建可靠且持久的应用程序。 垃圾回收**自动回收不可访问的未用对象所占用的内存。 可以为 null 的类型可防范不引用已分配对象的变量。 异常处理提供了一种结构化且可扩展的方法来进行错误检测和恢复。 Lambda 表达式支持函数编程技术。 语言集成查询 (LINQ) 语法创建一个公共模式,用于处理来自任何源的数据。 异步操作语言支持提供用于构建分布式系统的语法。 C# 有统一类型系统**。 **所有 C# 类型(包括 int
和 double
等基元类型)均继承自一个根 object
类型**。 所有类型共用一组通用运算。 任何类型的值都可以一致地进行存储、传输和处理。 此外,C# 还支持用户定义的引用类型和值类型。 C# 允许动态分配轻型结构的对象和内嵌存储。 C# 支持泛型方法和类型,因此增强了类型安全性和性能。 C# 可提供迭代器,使集合类的实现者可以定义客户端代码的自定义行为。
C# 强调版本控制,以确保程序和库以兼容方式随时间推移而变化。 C# 设计中受版本控制加强直接影响的方面包括:单独的 virtual
和 override
修饰符,关于方法重载决策的规则,以及对显式接口成员声明的支持。
2.1.2 .NET体系结构
C# 程序在 .NET 上运行,而 .NET 是名为公共语言运行时 (CLR) 的虚执行系统和一组类库。 CLR 是 Microsoft 对公共语言基础结构 (CLI) 国际标准的实现。 CLI 是创建执行和开发环境的基础,语言和库可以在其中无缝地协同工作。
用 C# 编写的源代码被编译成符合 CLI 规范的中间语言 (IL)。 IL 代码和资源(如位图和字符串)存储在扩展名通常为 .dll 的程序集中。 程序集包含一个介绍程序集的类型、版本和区域性的清单。
执行 C# 程序时,程序集将加载到 CLR。 CLR 会直接执行实时 (JIT) 编译,将 IL 代码转换成本机指令。 CLR 可提供其他与自动垃圾回收、异常处理和资源管理相关的服务。 CLR 执行的代码有时称为“托管代码”(而不是“非托管代码”),被编译成面向特定平台的本机语言。
语言互操作性是 .NET 的一项重要功能。 C# 编译器生成的 IL 代码符合公共类型规范 (CTS)。 通过 C# 生成的 IL 代码可以与通过 .NET 版本的 F#、Visual Basic、C++ 或其他 20 多种与 CTS 兼容的任何语言所生成的代码进行交互。 一个程序集可能包含多个用不同 .NET 语言编写的模块,且类型可以相互引用,就像是用同一种语言编写的一样。
2.1.3 类型和变量
类型定义 C# 中的任何数据的结构和行为。 类型的声明可以包含其成员、基类型、它实现接口和该类型允许的操作。 变量是用于引用特定类型的实例。
C# 有两种类型:值类型 和 引用类型。 值类型的变量直接包含它们的数据。 引用类型的变量存储对数据(称为“对象”)的引用。 对于引用类型,两个变量可以引用同一个对象;对一个变量执行的运算可能会影响另一个变量引用的对象。 借助值类型,每个变量都有自己的数据副本;因此,对一个变量执行的运算不会影响另一个变量(ref
和 out
参数变量除外)。
标识符为变量名称。 标识符是不包含任何空格的 unicode 字符序列。 如果标识符的前缀为 @
,则该标识符可以是 C# 保留字。 在与其他语言交互时,使用保留字作为标识符很有用。
C# 的值类型进一步分为:简单类型、枚举类型、结构类型、可以为 null 的值类型和元组值类型。 C# 引用类型又细分为类类型、接口类型、数组类型和委托类型。
- 值类型
- 简单类型
- 有符号整型:
sbyte
、short
、int
、long
- 无符号整型:
byte
、ushort
、uint
、ulong
- Unicode 字符:
char
,表示 UTF-16 代码单元 - IEEE 二进制浮点:
float
、double
- 高精度十进制浮点数:
decimal
- 布尔值:
bool
,表示布尔值(true
或false
)
- 有符号整型:
- 枚举类型
enum E {...}
格式的用户定义类型。enum
类型是一种包含已命名常量的独特类型。 每个enum
类型都有一个基础类型(必须是八种整型类型之一)。enum
类型的值集与基础类型的值集相同。
- 结构类型
- 格式为
struct S {...}
的用户定义类型
- 格式为
- 可以为 null 的值类型
- 值为
null
的其他所有值类型的扩展
- 值为
- 元组值类型
- 格式为
(T1, T2, ...)
的用户定义类型
- 格式为
- 简单类型
- 引用类型
- 类类型
- 其他所有类型的最终基类:
object
- Unicode 字符串:
string
,表示 UTF-16 代码单元序列 - 格式为
class C {...}
的用户定义类型
- 其他所有类型的最终基类:
- 接口类型
- 格式为
interface I {...}
的用户定义类型
- 格式为
- 数组类型
- 一维、多维和交错。 例如:
int[]
、int[,]
和int[][]
- 一维、多维和交错。 例如:
- 委托类型
- 格式为
delegate int D(...)
的用户定义类型
- 格式为
- 类类型
C# 程序使用 类型声明 创建新类型。 类型声明指定新类型的名称和成员。
class
类型定义包含数据成员(字段)和函数成员(方法、属性及其他)的数据结构。 类类型支持单一继承和多形性,即派生类可以扩展和专门针对基类的机制。struct
类型定义包含数据成员和函数成员的结构,这一点与类类型相似。 不过,与类不同的是,结构是值类型,通常不需要进行堆分配。 结构类型不支持用户指定的继承,并且所有结构类型均隐式继承自类型object
。interface
类型将协定定义为一组已命名的公共成员。 实现interface
的class
或struct
必须提供接口成员的实现代码。interface
可以继承自多个基接口,class
和struct
可以实现多个接口。delegate
类型表示引用包含特定参数列表和返回类型的方法。 通过委托,可以将方法视为可分配给变量并可作为参数传递的实体。 委托类同于函数式语言提供的函数类型。 它们还类似于其他一些语言中存在的“函数指针”概念。 与函数指针不同,委托是面向对象且类型安全的。
class
、struct
、interface
和 delegate
类型全部都支持泛型,因此可以使用其他类型对它们进行参数化。
C# 支持任意类型的一维和多维数组。 与上述类型不同,数组类型无需先声明即可使用。 相反,数组类型是通过在类型名称后面添加方括号构造而成。 例如,int[]
是 int
类型的一维数组,int[,]
是 int
类型的二维数组,int[][]
是由 int
类型的一维数组或“交错”数组构成的一维数组。
可以为 null 的类型不需要单独定义。 对于所有不可以为 null 的类型 T
,都有对应的可以为 null 的类型 T?
,后者可以包含附加值 null
。 例如,int?
是可保存任何 32 位整数或 null
值的类型,string?
是可以保存任何 string
或 null
值的类型。
C# 采用统一的类型系统,因此任意类型的值都可视为 object
。 每种 C# 类型都直接或间接地派生自 object
类类型,而 object
是所有类型的最终基类。 只需将值视为类型 object
,即可将引用类型的值视为对象。 通过执行 装箱 和 取消装箱操作,可以将值类型的值视为对象。 在以下示例中,int
值被转换成 object
,然后又恢复成 int
。
1 | int i = 123; |
将值类型的值分配给 object
对象引用时,会分配一个“箱”来保存此值。 该箱是引用类型的实例,此值会被复制到该箱。 相反,当 object
引用被显式转换成值类型时,将检查引用的 object
是否是具有正确值类型的箱。 如果检查成功,则会将箱中的值复制到值类型。
C# 的统一类型系统实际上意味着“按需”将值类型视为 object
引用。 鉴于这种统一性,使用类型 object
的常规用途库可以与派生自 object
的所有类型结合使用,包括引用类型和值类型。
2.1.4 程序结构
C# 中的关键组织结构概念包括程序、命名空间、类型、成员和程序集。 程序声明类型,而类型则包含成员,并被整理到命名空间中。 类型示例包括类、结构和接口。 成员示例包括字段、方法、属性和事件。 编译完的 C# 程序实际上会打包到程序集中。 程序集的文件扩展名通常为 .exe
或 .dll
,具体视其分别实现的是应用程序还是库而定。
1 | using System; |
此类的完全限定的名称为 Acme.Collections.Stack
。 此类包含多个成员:一个 top
字段、两个方法(Push
和 Pop
)和一个 Entry
嵌套类。 Entry
类还包含三个成员:一个 next
字段、一个 data
字段和一个构造函数。 Stack
是泛型类。 它具有一个类型参数 T
,在使用时替换为具体类型。
堆栈是一个“先进后出”(FILO) 集合。 添加到堆栈顶部的新元素。 删除元素时,将从堆栈顶部删除该元素。 前面的示例声明定义堆栈的存储和行为的 Stack
类型。 可以声明一个引用 Stack
类型的实例的变量来使用该功能。
程序集包含中间语言 (IL) 指令形式的可执行代码和元数据形式的符号信息。 执行前,.NET 公共语言运行时的实时 (JIT) 编译器会将程序集中的 IL 代码转换为特定于处理器的代码。
由于程序集是包含代码和元数据的自描述功能单元,因此无需在 C# 中使用 #include
指令和头文件。 只需在编译程序时引用特定的程序集,即可在 C# 程序中使用此程序集中包含的公共类型和成员。 例如,此程序使用 acme.dll
程序集中的 Acme.Collections.Stack
类:
1 | using System; |
若要编译此程序,需要引用包含前面示例中定义的堆栈类的程序集。
C# 程序可以存储在多个源文件中。 在编译 C# 程序时,将同时处理所有源文件,并且源文件可以自由地相互引用。 从概念上讲,就好像所有源文件在被处理之前都连接到一个大文件。 在 C# 中永远都不需要使用前向声明,因为声明顺序无关紧要(极少数例外情况除外)。 C# 并不限制源文件只能声明一种公共类型,也不要求源文件的文件名必须与其中声明的类型相匹配。
2.2 类型
2.2.1 类型和成员
作为面向对象的语言,C# 支持封装、继承和多态性这些概念。 类可能会直接继承一个父类,并且可以实现任意数量的接口。 若要用方法重写父类中的虚方法,必须使用 override
关键字,以免发生意外重定义。 在 C# 中,结构就像是轻量级类,是可以实现接口但不支持继承的堆栈分配类型。 C# 还可提供记录,这些记录是主要用于存储数据值的类类型。
2.2.2 类和对象
类 是最基本的 C# 类型。 类是一种数据结构,可在一个单元中就将状态(字段)和操作(方法和其他函数成员)结合起来。 类为类实例(亦称为“对象”)提供了定义 。 类支持 继承 和 多形性,即 派生类 可以扩展和专门针对 基类 的机制。
新类使用类声明进行创建。 类声明以标头开头。 标头指定以下内容:
- 类的特性和修饰符
- 类的名称
- 基类(从基类继承时)
- 接口由该类实现。
标头后面是类主体,由在分隔符 {
和 }
内编写的成员声明列表组成。
以下代码展示的是简单类 Point
的声明:
1 | public class Point |
类实例是使用 new
运算符进行创建,此运算符为新实例分配内存,调用构造函数来初始化实例,并返回对实例的引用。 以下语句创建两个 Point
对象,并将对这些对象的引用存储在两个变量中:
C#复制
1 | var p1 = new Point(0, 0); |
当无法再访问对象时,对象占用的内存会被自动回收。 没有必要也无法在 C# 中显式解除分配对象。
2.2.3 结构
类定义可支持继承和多形性的类型。 它们使你能够基于派生类的层次结构创建复杂的行为。 相比之下,结构类型是较为简单的类型,其主要目的是存储数据值。 结构不能声明基类型;它们从 System.ValueType 隐式派生。 不能从 struct
类型派生其他 struct
类型。 这些类型已隐式密封。
1 | public struct Point |
2.2.4 接口
接口定义了可由类和结构实现的协定。 定义接口来声明在不同类型之间共享的功能。 例如,System.Collections.Generic.IEnumerable 接口定义了一个遍历集合(如数组)中所有项的一致方法。 接口可以包含方法、属性、事件和索引器。 接口通常不提供所定义成员的实现,仅指定必须由实现接口的类或结构提供的成员。
接口可以采用 多重继承。 在以下示例中,接口 IComboBox
同时继承自 ITextBox
和 IListBox
。
1 | interface IControl |
类和结构可以实现多个接口。 在以下示例中,类 EditBox
同时实现 IControl
和 IDataBound
。
1 | interface IDataBound |
当类或结构实现特定接口时,此类或结构的实例可以隐式转换成相应的接口类型。 例如
1 | EditBox editBox = new EditBox(); |