简介:C#是一种由微软开发的面向对象编程语言,主要用于开发Windows桌面、Web和移动平台应用程序。它以其简洁、类型安全和现代设计哲学为特点,支持.NET框架和.NET Core平台,允许跨平台应用开发。C#的关键特性包括静态类型系统、面向对象编程、命名空间、方法与函数、控制流语句、异常处理、Lambda表达式、LINQ、异步编程、泛型、属性、事件处理、匿名类型和动态类型、元数据和反射、异构集合以及C# 9.0的自动属性和记录类型。此外,它还包括可空引用类型和模式匹配等现代化特性。学习C#对于构建高效和可靠的软件系统至关重要。
1. C#概述及跨平台特性
在本章中,我们将带你快速了解C#语言的基础知识以及它如何演化成支持跨平台开发的强大工具。首先,我们将介绍C#的语言起源和设计哲学,然后再探讨其关键的跨平台特性,这将为后续章节中更深入的技术细节打下基础。
1.1 C#语言起源与特性
C#(发音为“看”)是一种由微软开发的现代、类型安全的面向对象编程语言。它是在.NET框架下运行的,旨在为开发人员提供一个结构化和强大语言来构建各种应用程序。从C# 1.0版本开始,该语言就不断进化,增加新特性和改进,使其成为一个成熟的选择,用于构建企业级应用程序、游戏开发、云计算等。
1.2 跨平台开发
随着.NET Core的推出,C#已经突破了Windows的限制,成为一种跨平台的开发语言。.NET Core是一个开源、跨平台的运行时,支持Windows、Linux和macOS等多个操作系统。这意味着开发者可以使用C#语言编写代码,然后将应用程序部署到多种操作系统上,而无需进行重大修改。
开发者现在可以通过多种方式访问.NET Core: - 使用.NET CLI (命令行界面) 执行脚本和编译程序。 - 使用集成开发环境(IDEs),比如Visual Studio,为跨平台应用提供支持。 - 利用Docker容器,可以进一步简化开发和部署过程。
// 示例代码:使用.NET Core在命令行创建一个简单的“Hello World”程序
using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
通过本章的介绍,你将对C#有一个初步的认识,并了解到它作为跨平台语言的能力。接下来的章节将深入探讨C#的语言特性,让我们开始吧。
2. ```
第二章:类型系统和静态类型语言
2.1 C#的类型系统基础
2.1.1 值类型与引用类型的区别
C#的类型系统定义了两种基本的类型类别:值类型和引用类型。值类型存储在栈上,直接包含数据。当创建值类型实例时,会在栈内存分配空间,并在声明变量时直接初始化。每个值类型的变量都是该类型数据的完整副本。常见的值类型包括整型(int)、浮点型(float)、布尔型(bool)、结构体(struct)等。
引用类型存储在堆上,变量中存储的是对数据的引用。在创建引用类型的实例时,仅分配一个指针大小的空间在栈上,而实际的数据存储在堆上,这个堆上的内存通过引用访问。这使得引用类型可以进行动态内存管理,但同时也带来了垃圾回收和内存碎片的问题。常见的引用类型包括类(class)、接口(interface)、数组(array)等。
2.1.2 类型转换和类型安全
在C#中,类型转换确保了数据的正确处理和类型安全。C#提供了多种类型转换方式,包括隐式转换和显式转换。隐式转换是自动进行的,不会导致数据丢失,而显式转换需要程序员明确指定,并且可能会导致数据丢失或精度降低。
隐式转换的例子包括从小范围整型自动转换为大范围整型(如int转为long),或者从派生类转换为基类。显式转换需要使用强制类型转换,比如将double转换为int,这可能会导致数据的截断。
类型安全是指在编译时就能够检查出类型不匹配的问题,这有助于避免运行时错误。C#作为静态类型语言,可以在编译时检查数据类型和变量的使用,确保类型的操作是安全的。
2.2 C#中的静态类型语言特征
2.2.1 静态类型语言的优势
静态类型语言,如C#,在编译时进行类型检查,这提供了几个重要的优势: - 错误检测: 由于在编译时期就对类型进行了检查,因此可以及早发现类型不匹配的问题,提高了开发效率。 - 代码清晰性: 类型声明提供了变量和方法期望的数据类型的明确信息,使得代码更加易于理解和维护。 - 代码优化: 编译器可以对代码进行优化,因为类型信息在编译时是已知的。 - 工具支持: 开发工具可以提供更好的智能感知(IntelliSense)、代码重构和静态代码分析支持。
2.2.2 静态类型语言与动态类型语言的对比
与静态类型语言不同,动态类型语言(如Python或JavaScript)的类型在运行时确定,而不是在编译时。这两种类型语言各有优缺点:
静态类型语言优点: - 早期错误发现: 在编码阶段就能发现很多类型错误。 - 性能优化: 编译器可以优化代码,因为类型已知。 - 维护性和可读性: 代码更加结构化,易于理解和维护。
动态类型语言优点: - 灵活性: 代码更加灵活,易于编写和修改。 - 简洁的语法: 较少的类型声明让代码更简洁。 - 快速原型开发: 开发快速原型时速度更快。
2.2.3 C#中的类型推理和类型推断
C#提供了类型推理(Type Inference)的功能,允许在声明变量时不必显式指定类型,编译器可以自动推断出变量的类型。这主要通过 var
关键字实现。例如:
var number = 10; // number is inferred to be of type 'int'
类型推断不是类型转换,它只是在编译时进行的类型确认过程,不会引入运行时开销。它使得代码更加简洁,同时保持了静态类型语言的安全性和性能优势。
类型推断允许在不牺牲类型安全性的情况下编写更简洁的代码,但要谨慎使用,避免过度依赖 var
,以至于降低了代码的可读性。
在本章节中,我们探讨了C#类型系统的基础知识,包括值类型与引用类型的区别,类型转换和类型安全等核心概念。接着,我们对比了静态类型语言和动态类型语言的优势与劣势,并深入分析了C#中类型推理和类型推断的使用方法和优势。这些内容帮助读者理解C#类型系统如何使程序更加可靠、易于维护,同时提供了一些实用的编程实践建议。
# 3. 面向对象编程的三大特性
面向对象编程(OOP)是一种通过对象来组织软件设计的编程范式,其核心概念包括封装、继承和多态。这些特性使得代码更加模块化、易于重用,并且可以更好地模拟现实世界的复杂性。C#语言在设计时充分考虑了这些面向对象的特性,使得它成为构建复杂应用程序的强有力工具。
## 3.1 封装
封装是面向对象编程的基础,它涉及将数据(或状态)和操作数据的方法捆绑成单个单元,即对象。封装隐藏了对象的内部实现细节,只暴露必要的接口给外部访问。这样做的好处是,可以保护对象的内部状态不受外部的干扰和破坏,同时简化了接口的使用。
### 3.1.1 访问修饰符的使用
C#提供了一系列访问修饰符来控制类成员的可见性,这些访问修饰符包括`public`、`protected`、`internal`、`protected internal`、`private`和`private protected`。选择合适的访问修饰符可以帮助开发者实现良好的封装性。
- `public`:类成员可以被任何其他代码访问。
- `protected`:类成员仅对包含它的类及其派生类可见。
- `internal`:类成员只在当前项目内可见。
- `protected internal`:类成员在当前程序集或派生类中可见。
- `private`:类成员只能在包含它的类中被访问。
- `private protected`:类成员在包含类和同一程序集中的派生类中可见。
```csharp
public class Vehicle
{
private string _model; // 私有字段,只能在Vehicle类内部访问
public string Model // 公有属性,任何代码都可以访问
{
get { return _model; }
set { _model = value; }
}
}
在这个例子中, _model
是一个私有字段,它不能直接从 Vehicle
类之外访问。外部代码必须通过 Model
属性来访问这个字段,这允许我们在赋值时进行验证,从而确保数据的有效性和完整性。
3.1.2 属性与字段的区别及应用
在C#中,属性(Properties)是一种特殊的成员,它提供了一种方式来读取、编写或计算私有字段的值。属性是面向对象编程中的封装性的一种体现,它允许我们隐藏字段的实现细节,并提供灵活的访问控制。
- 字段(Fields):是类中存储数据的基础成员。字段可以是静态的(属于类)或实例的(属于对象)。
- 属性(Properties):可以有获取(get)和设置(set)访问器,用来控制字段的读取和赋值操作。
public class Person
{
private string _name; // 私有字段
public string Name // 公有属性
{
get { return _name; }
set { _name = value; }
}
}
在上面的 Person
类中, _name
字段是私有的,不能直接访问,而 Name
属性则提供了对 _name
字段的封装访问。通过属性,我们可以添加逻辑来控制数据的读取和设置,例如进行验证或转换操作。
3.2 继承
继承是面向对象编程中的另一个核心特性,它允许我们定义一个类(基类)的属性和方法可以被另一个类(派生类)继承。继承简化了代码的重用,同时也使系统结构更加清晰。
3.2.1 类的继承机制
在C#中,使用冒号 :
和基类名来实现继承,派生类会继承基类的所有公共成员和保护成员。C#不支持多重继承,但可以通过接口来模拟多重继承的行为。
public class Animal
{
public string Name { get; set; }
public virtual void Speak() // virtual表示可以被重写
{
Console.WriteLine("This animal makes a sound");
}
}
public class Dog : Animal // Dog继承自Animal
{
public override void Speak() // override重写基类中的方法
{
Console.WriteLine("Woof!");
}
}
在这个例子中, Dog
类继承了 Animal
类,并重写了 Speak
方法。继承使得 Dog
能够使用 Animal
类定义的 Name
属性以及 Speak
方法的基础实现,并且可以扩展或修改这些方法。
3.2.2 方法覆盖和抽象类的实现
方法覆盖(Method Overriding)允许派生类提供基类方法的一个特定实现。在C#中,使用 virtual
关键字标记基类中的方法,派生类使用 override
关键字来重写该方法。
抽象类是一种不能被实例化的基类,它通常包含一个或多个抽象方法。抽象方法是没有实现的方法,它必须被任何非抽象的派生类覆盖。
public abstract class Shape
{
public abstract double CalculateArea(); // 抽象方法
}
public class Circle : Shape
{
private double _radius;
public Circle(double radius)
{
_radius = radius;
}
public override double CalculateArea()
{
return Math.PI * _radius * _radius;
}
}
在上面的例子中, Shape
是一个抽象类,它定义了一个抽象方法 CalculateArea
。 Circle
类继承自 Shape
并实现了 CalculateArea
方法来计算圆的面积。由于 Shape
类是抽象的,它不能被直接实例化,而是需要通过其派生类,如 Circle
来创建对象。
3.3 多态
多态是面向对象编程中的一项重要特性,它允许开发者以统一的方式处理对象,而无需关心对象的具体类型。多态性通过继承和接口实现,可以提高代码的灵活性和可扩展性。
3.3.1 多态的概念与实现
多态允许一个对象在不同的上下文中表现出不同的行为。在C#中,多态最常见的形式是方法重载和方法重写。
- 方法重载(Method Overloading):在同一个类中定义多个同名方法,但它们具有不同的参数列表。
- 方法重写(Method Overriding):在派生类中覆盖基类的方法。
public class Vehicle
{
public virtual void Start()
{
Console.WriteLine("Vehicle starts.");
}
}
public class Car : Vehicle
{
public override void Start()
{
Console.WriteLine("Car starts with a roar.");
}
}
public class Boat : Vehicle
{
public override void Start()
{
Console.WriteLine("Boat starts with a hum.");
}
}
// 使用
Vehicle vehicle = new Car();
vehicle.Start(); // 输出: Car starts with a roar.
vehicle = new Boat();
vehicle.Start(); // 输出: Boat starts with a hum.
在这个例子中,我们定义了一个基类 Vehicle
,以及两个派生类 Car
和 Boat
。基类有一个虚方法 Start
,在每个派生类中重写以提供特定的启动信息。这种多态的实现使得我们可以通过基类类型的引用来调用 Start
方法,并且根据对象的实际类型来确定应该执行哪个版本的方法。
3.3.2 接口与抽象类的多态应用
接口是C#中定义方法、属性、事件或索引器的引用类型。与抽象类不同,接口可以被多继承,即一个类可以实现多个接口。
public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("Woof!");
}
}
public class Cat : IAnimal
{
public void Speak()
{
Console.WriteLine("Meow!");
}
}
// 使用
IAnimal myDog = new Dog();
myDog.Speak(); // 输出: Woof!
IAnimal myCat = new Cat();
myCat.Speak(); // 输出: Meow!
在这个例子中, IAnimal
是一个接口,它定义了一个 Speak
方法。 Dog
和 Cat
类都实现了 IAnimal
接口,并提供了 Speak
方法的具体实现。当通过 IAnimal
接口类型的变量来引用 Dog
或 Cat
的实例时,可以调用 Speak
方法,这种调用将根据对象的实际类型来执行相应的方法,这就是接口实现的多态性。
结语
通过封装、继承和多态这三大面向对象编程特性,C#为开发者提供了强大的工具来构建结构良好、高度可维护和可扩展的代码库。封装保护了数据安全,继承促进了代码复用,多态增强了系统灵活性。掌握这些特性对于任何C#开发者而言都是至关重要的。在下一章节中,我们将深入探讨代码结构和控制流的高级概念,包括命名空间的使用、方法的定义、控制流语句的应用等,这些都是构建复杂应用程序不可或缺的组成部分。
4. 代码结构和控制流
4.1 命名空间和代码组织
4.1.1 命名空间的作用和使用
命名空间在C#中是一种对逻辑分组的标识符。它主要用来组织代码,避免名称冲突,并提供一种访问命名实体的层次方式。命名空间在项目中经常被用来对类、接口、委托、枚举、结构等进行分类和封装。这种封装机制有助于构建可重用和易于维护的代码库。
要使用命名空间,你可以使用 namespace
关键字来声明一个新的命名空间。例如:
namespace MyCompany.Project
{
public class MyClass {}
}
在上述代码中, MyCompany.Project
作为命名空间,而 MyClass
类则位于这个命名空间下。
为了使用在其他命名空间中定义的类型,可以使用 using
关键字:
using MyCompany.Project;
public class Program
{
public static void Main(string[] args)
{
MyClass instance = new MyClass();
}
}
在这个例子中, using
语句使得 MyClass
可以直接被访问,而不需要指定完整的命名空间 MyCompany.Project.MyClass
。
4.1.2 项目的模块化组织技巧
随着项目规模的增加,代码的组织变得至关重要。在C#中,利用命名空间可以将项目分解成多个模块或组件。模块化有助于将不同功能的代码分隔开来,并且每个模块都可以单独进行测试和维护。
一个良好的模块化实践是使用功能或层次结构来划分命名空间,比如:
-
MyCompany.Project.Data
:数据访问层 -
MyCompany.Project.Models
:数据模型 -
MyCompany.Project.Services
:业务逻辑层 -
MyCompany.Project.Web
:Web展示层
确保在每个命名空间内只关注其职责范围内的功能,这有助于维护代码的清晰性和易读性。同时,合理使用嵌套命名空间来表示代码之间的依赖关系和层次结构,可以增强项目的整体可管理性。
4.2 方法与函数的基本概念
4.2.1 方法的定义和调用
方法是C#程序中的基本构成单位,它封装了一系列的代码语句,这些语句可以执行特定任务并可重复使用。一个方法的定义包括访问修饰符、返回类型、方法名、参数列表和方法体。调用方法则是执行这些封装好的语句。
定义方法的语法如下:
访问修饰符 返回类型 方法名(参数列表)
{
// 方法体
return 返回值;
}
一个简单的例子:
public int Add(int a, int b)
{
return a + b;
}
此方法接受两个整数参数,并返回它们的和。调用此方法的代码如下:
int sum = Add(3, 4);
Console.WriteLine(sum); // 输出结果为 7
4.2.2 函数参数和返回值
函数参数是方法定义中用于输入值的变量。方法可以通过参数接收外部传入的数据,这些数据可以是值类型或引用类型。在C#中,可以使用 ref
或 out
关键字来传递引用参数,允许方法修改传入的变量值。
返回值是指方法执行完毕后,可以返回给调用者的值。返回值的类型必须与方法声明中的返回类型相匹配。如果方法不返回任何值,则其返回类型为 void
。
考虑一个使用引用参数和返回值的方法:
public void Increment(ref int number)
{
number++;
}
public int Add(int a, int b)
{
return a + b;
}
调用 Increment
方法并传入引用参数:
int value = 5;
Increment(ref value);
Console.WriteLine(value); // 输出结果为 6
调用 Add
方法并接收返回值:
int sum = Add(3, 4);
Console.WriteLine(sum); // 输出结果为 7
4.3 控制流语句的应用
4.3.1 条件语句的深入理解和最佳实践
条件语句允许根据不同的条件执行不同的代码块。在C#中,最常见的条件语句是 if
、 else if
和 else
结构,以及 switch
语句。
当使用 if
语句时,需要根据条件表达式的结果来执行不同的代码块。例如:
if (condition)
{
// 条件为真时执行的代码
}
else if (anotherCondition)
{
// 另一个条件为真时执行的代码
}
else
{
// 所有条件都不满足时执行的代码
}
使用 switch
语句时,可以根据特定的值执行不同的分支,通常用于替代一系列的 if-else
语句,特别是在值需要匹配多个常量时更为方便:
switch (value)
{
case 1:
// 值为1时执行的代码
break;
case 2:
// 值为2时执行的代码
break;
default:
// 默认情况下执行的代码
break;
}
在设计条件语句时,应该遵循以下最佳实践:
- 尽量保持条件简单,避免使用复杂的逻辑表达式,以便代码更易于理解。
- 使用
switch
语句替代if-else
结构,当需要匹配一组固定值时。 - 避免在
if
条件中使用赋值语句(例如if (a = b)
),因为这实际上是一个赋值操作,并总是返回true
。 - 尽量减少嵌套层次,可以通过重构提取公共部分到单独的方法中。
4.3.2 循环语句的优化和选择
循环语句用于重复执行一段代码直到满足特定条件。C#提供了几种循环控制结构: for
、 foreach
、 while
和 do-while
。
-
for
循环适用于已知迭代次数的情况。 -
foreach
循环用于遍历集合中的每个元素,适用于不需要索引访问的场景。 -
while
循环适用于在循环开始之前不知道迭代次数的情况。 -
do-while
循环确保至少执行一次循环体,即使条件一开始就不满足。
选择合适的循环结构可以优化代码的性能和可读性。考虑以下例子:
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
foreach (var item in collection)
{
Console.WriteLine(item);
}
int i = 0;
while (i < 10)
{
Console.WriteLine(i);
i++;
}
int j = 0;
do
{
Console.WriteLine(j);
j++;
} while (j < 10);
循环优化的关键是减少不必要的计算,提高循环效率:
- 尽可能在循环外计算不变的表达式,减少每次迭代中的计算量。
- 避免在循环内部使用过多的嵌套,如果必须使用,可以考虑使用方法分解。
- 使用
break
语句在适当的时候提前退出循环,避免无意义的迭代。 - 使用
continue
语句跳过当前迭代中不需要执行的代码块。
通过合理选择和优化循环语句,可以显著提高代码的运行效率和可读性。
5. C#高级编程技巧与新特性
5.1 异常处理机制
异常处理是任何编程语言中不可或缺的一部分,它允许程序在遇到错误情况时能够优雅地处理异常情况,并恢复到一个安全状态。C#提供了一套完整的异常处理机制,包括try, catch, finally以及throw关键字。
5.1.1 异常处理的基本概念
在C#中,当发生错误或异常情况时,系统会抛出一个异常对象。这个对象包含了关于异常的信息,如异常类型、错误信息、调用堆栈等。开发者可以使用try块包围可能会抛出异常的代码,并使用catch块捕获和处理特定类型的异常。finally块则无论是否捕获到异常都会执行。
try
{
// 尝试执行的代码,可能抛出异常
}
catch (ExceptionType ex)
{
// 捕获并处理特定类型的异常
}
finally
{
// 清理资源,无论是否捕获到异常都会执行
}
5.1.2 自定义异常和异常过滤器
自定义异常允许开发者创建更具体、针对性强的异常类型。开发者可以通过继承Exception类来创建自定义异常。异常过滤器提供了一种更灵活的方式来决定是否处理异常,它允许在catch块中添加过滤条件。
public class CustomException : Exception
{
public CustomException(string message) : base(message) { }
}
try
{
// 可能抛出异常的代码
}
catch (CustomException ex) when (ex.Message == "Expected Exception")
{
// 只有当异常消息符合特定条件时,才处理该异常
}
5.2 Lambda表达式和LINQ的使用
Lambda表达式和LINQ是C#语言中增强代码可读性和简洁性的工具,它们在处理集合数据时特别有用。
5.2.1 Lambda表达式的应用
Lambda表达式是一种简化委托声明的语法,它允许快速编写内联表达式。Lambda表达式使用参数 => 表达式或语句块的方式定义。
Func<int, int> square = x => x * x;
int result = square(5); // 结果为25
5.2.2 LINQ的查询表达式和方法语法
LINQ(Language Integrated Query)提供了一种统一的方法来查询数据,无论是内存中的集合还是数据库中的数据。LINQ查询表达式和方法语法是两种常用的数据查询方式。
// 使用查询表达式
var query = from student in students
where student.Age > 18
select student.Name;
// 使用方法语法
var queryMethodSyntax = students.Where(student => student.Age > 18)
.Select(student => student.Name);
5.3 异步编程方法
随着应用程序变得越来越复杂,异步编程变得越来越重要。异步编程允许程序在等待长时间运行的任务完成时不阻塞线程。
5.3.1 异步编程的基本原理
异步编程通过异步操作允许程序继续执行其他工作,而不是等待阻塞操作完成。在C#中,这是通过async和await关键字实现的。
public async Task<string> DownloadWebPageAsync(string url)
{
HttpClient client = new HttpClient();
return await client.GetStringAsync(url);
}
5.3.2 async和await的使用和最佳实践
async和await关键字极大地简化了异步编程模型。async声明一个异步方法,而await则用于等待异步操作的完成。这些关键字的使用使得代码更加清晰和易于维护。
public async Task MainAsync()
{
string content = await DownloadWebPageAsync("***");
Console.WriteLine(content.Length);
}
5.4 泛型编程概念
泛型编程允许编写与数据类型无关的代码,从而提高代码的复用性和类型安全。
5.4.1 泛型的定义和使用
泛型类和泛型方法能够在定义时不必指定具体的类型,而是在使用时才指定类型。
public class GenericList<T>
{
private List<T> _list = new List<T>();
public void Add(T item) => _list.Add(item);
}
GenericList<int> intList = new GenericList<int>();
intList.Add(1);
5.4.2 泛型方法和泛型类的进阶应用
泛型方法和泛型类可以用于创建灵活的算法和数据结构。泛型类可以包含非泛型成员,泛型方法可以定义在非泛型类中。
public class Utils
{
public static T Max<T>(T first, T second) where T : IComparable
{
***pareTo(second) > 0 ? first : second;
}
}
5.5 C#新版本特性介绍
C#是不断进化的语言,每一代的更新都会带来新的特性和改进,以适应现代编程的需求。
5.5.1 C# 9.0的新特性:自动属性和记录类型
C# 9.0引入了自动实现的属性的简写形式,以及记录类型(record),它是一种特殊的类,用于表示不可变数据。
public record Person(string Name, int Age);
Person person = new("John Doe", 30);
5.5.2 C# 8.0的空引用安全特性
C# 8.0引入了空引用安全特性,如可为空引用类型,使开发者能更明确地表达对null值的意图,并减少空引用异常的风险。
string? name = "Alice";
// name 必须是非空的,因为它是非可空引用类型
Console.WriteLine(name.Length);
5.5.3 模式匹配技术的演进
模式匹配是一种表达式形式,它允许检查对象是否符合某些条件,并将其分解为其组成部分。C# 9.0进一步增强了模式匹配的能力。
int Sum(object o)
{
return o switch
{
null => 0,
int i => i,
List<int> list => list.Sum(),
_ => throw new InvalidOperationException("Not a valid type")
};
}
以上章节深入地探讨了C#的高级编程技巧和近年来引入的新特性,展示了如何在实际开发中运用这些特性来编写更加健壮、高效和可维护的代码。
简介:C#是一种由微软开发的面向对象编程语言,主要用于开发Windows桌面、Web和移动平台应用程序。它以其简洁、类型安全和现代设计哲学为特点,支持.NET框架和.NET Core平台,允许跨平台应用开发。C#的关键特性包括静态类型系统、面向对象编程、命名空间、方法与函数、控制流语句、异常处理、Lambda表达式、LINQ、异步编程、泛型、属性、事件处理、匿名类型和动态类型、元数据和反射、异构集合以及C# 9.0的自动属性和记录类型。此外,它还包括可空引用类型和模式匹配等现代化特性。学习C#对于构建高效和可靠的软件系统至关重要。