一、委托、事件和 Lambda 表达式
1.1 委托的定义、使用和组合
1.1.1 委托的定义
委托是一种引用类型,它可以封装一个或多个方法,并且可以像调用方法一样调用委托。委托的定义语法如下:
delegate 返回类型 委托名(参数列表);
例如,定义一个委托用于封装一个接受两个整数并返回整数的方法:
delegate int MathOperation(int a, int b);
1.1.2 委托的使用
定义好委托后,就可以使用它来封装具体的方法。以下是一个完整的示例:
using System;
delegate int MathOperation(int a, int b);
class Program
{
static int Add(int a, int b)
{
return a + b;
}
static int Subtract(int a, int b)
{
return a - b;
}
static void Main()
{
// 创建委托实例并关联 Add 方法
MathOperation operation = Add;
int result = operation(5, 3);
Console.WriteLine($"5 + 3 = {result}");
// 重新关联 Subtract 方法
operation = Subtract;
result = operation(5, 3);
Console.WriteLine($"5 - 3 = {result}");
}
}
1.1.3 委托的组合
委托支持多播,即一个委托实例可以封装多个方法。使用 += 运算符可以将多个方法添加到委托实例中,使用 -= 运算符可以移除某个方法。示例如下:
using System;
delegate void MessagePrinter();
class Program
{
static void PrintHello()
{
Console.WriteLine("Hello");
}
static void PrintWorld()
{
Console.WriteLine("World");
}
static void Main()
{
// 创建委托实例并组合多个方法
MessagePrinter printer = PrintHello;
printer += PrintWorld;
// 调用委托,依次执行封装的方法
printer();
// 移除一个方法
printer -= PrintHello;
printer();
}
}
1.2 事件的声明、订阅和发布
1.2.1 事件的声明
事件是基于委托的一种特殊类型,用于实现对象间的消息传递。事件的声明通常使用 event 关键字,结合委托类型。示例如下:
using System;
// 定义事件委托
public delegate void EventHandler();
public class Publisher
{
// 声明事件
public event EventHandler MyEvent;
public void RaiseEvent()
{
// 触发事件
MyEvent?.Invoke();
}
}
1.2.2 事件的订阅和发布
事件的订阅是指将一个方法关联到事件上,发布则是触发事件。以下是一个完整的示例:
using System;
// 定义事件委托
public delegate void EventHandler();
public class Publisher
{
// 声明事件
public event EventHandler MyEvent;
public void RaiseEvent()
{
// 触发事件
MyEvent?.Invoke();
}
}
public class Subscriber
{
public void HandleEvent()
{
Console.WriteLine("Event handled.");
}
}
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
// 订阅事件
publisher.MyEvent += subscriber.HandleEvent;
// 发布事件
publisher.RaiseEvent();
}
}
1.3 Lambda 表达式和匿名方法
1.3.1 Lambda 表达式的语法
Lambda 表达式是一种简洁的创建匿名方法的方式。其基本语法为:
(参数列表) => 表达式或语句块
例如,使用 Lambda 表达式实现一个简单的加法操作:
using System;
class Program
{
static void Main()
{
Func add = (a, b) => a + b;
int result = add(5, 3);
Console.WriteLine($"5 + 3 = {result}");
}
}
1.3.2 Lambda 表达式的应用场景
Lambda 表达式常用于 LINQ 查询、事件处理等场景。例如,在 LINQ 查询中筛选集合元素:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List numbers = new List { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
}
}
1.3.3 匿名方法
匿名方法是在 C# 早期版本中用于创建临时方法的方式,Lambda 表达式是其更简洁的替代方案。示例如下:
using System;
delegate int MathOperation(int a, int b);
class Program
{
static void Main()
{
MathOperation operation = delegate (int a, int b)
{
return a + b;
};
int result = operation(5, 3);
Console.WriteLine($"5 + 3 = {result}");
}
}
二、泛型
2.1 泛型类、泛型方法和泛型接口
2.1.1 泛型类
泛型类允许在类的定义中使用类型参数,从而可以在实例化类时指定具体的类型。示例如下:
using System;
// 定义泛型类
public class GenericList
{
private T[] items = new T[10];
private int count = 0;
public void Add(T item)
{
if (count < items.Length)
{
items[count++] = item;
}
}
public T GetItem(int index)
{
if (index < count)
{
return items[index];
}
throw new IndexOutOfRangeException();
}
}
class Program
{
static void Main()
{
// 实例化泛型类,指定类型为 int
GenericList intList = new GenericList();
intList.Add(5);
int item = intList.GetItem(0);
Console.WriteLine(item);
// 实例化泛型类,指定类型为 string
GenericList stringList = new GenericList();
stringList.Add("Hello");
string str = stringList.GetItem(0);
Console.WriteLine(str);
}
}
2.1.2 泛型方法
泛型方法允许在方法的定义中使用类型参数。示例如下:
using System;
class Program
{
static T Max(T a, T b) where T : IComparable
{
return a.CompareTo(b) >= 0 ? a : b;
}
static void Main()
{
int maxInt = Max(5, 3);
Console.WriteLine($"Max of 5 and 3: {maxInt}");
string maxString = Max("apple", "banana");
Console.WriteLine($"Max of 'apple' and 'banana': {maxString}");
}
}
2.1.3 泛型接口
泛型接口允许在接口的定义中使用类型参数。示例如下:
using System;
// 定义泛型接口
public interface IGenericInterface
{
T GetValue();
void SetValue(T value);
}
// 实现泛型接口
public class GenericClass : IGenericInterface
{
private T value;
public T GetValue()
{
return value;
}
public void SetValue(T value)
{
this.value = value;
}
}
class Program
{
static void Main()
{
IGenericInterface genericInt = new GenericClass();
genericInt.SetValue(5);
int result = genericInt.GetValue();
Console.WriteLine(result);
}
}
2.2 泛型约束
泛型约束用于限制泛型类型参数的范围,确保在泛型代码中可以安全地使用类型参数的成员。常见的泛型约束有以下几种:
2.2.1where T : struct
约束类型参数必须是值类型。示例如下:
using System;
class Program
{
static void PrintValue(T value) where T : struct
{
Console.WriteLine(value);
}
static void Main()
{
int number = 5;
PrintValue(number);
// 以下代码会编译错误,因为 string 不是值类型
// string str = "Hello";
// PrintValue(str);
}
}
2.2.2where T : class
约束类型参数必须是引用类型。示例如下:
using System;
class Program
{
static void PrintObject(T obj) where T : class
{
if (obj != null)
{
Console.WriteLine(obj.ToString());
}
}
static void Main()
{
string str = "Hello";
PrintObject(str);
// 以下代码会编译错误,因为 int 不是引用类型
// int number = 5;
// PrintObject(number);
}
}
2.2.3where T : new()
约束类型参数必须有一个无参构造函数。示例如下:
using System;
class Program
{
static T CreateInstance() where T : new()
{
return new T();
}
static void Main()
{
var instance = CreateInstance();
Console.WriteLine(instance.GetType().Name);
}
}
class MyClass
{
public MyClass()
{
}
}
2.2.4where T : 基类名或where T : 接口名
约束类型参数必须是指定基类的派生类或实现了指定接口。示例如下:
using System;
public interface IMyInterface
{
void DoSomething();
}
public class MyClass : IMyInterface
{
public void DoSomething()
{
Console.WriteLine("Doing something...");
}
}
class Program
{
static void CallDoSomething(T obj) where T : IMyInterface
{
obj.DoSomething();
}
static void Main()
{
MyClass myClass = new MyClass();
CallDoSomething(myClass);
}
}
三、异步编程
3.1 异步编程模型
异步编程是一种编程范式,用于处理可能会阻塞线程的操作,如网络请求、文件读写等,以提高程序的响应性和性能。在 .NET 中,异步编程主要通过 async 和 await 关键字来实现。
3.2Task和ValueTask
3.2.1Task
Task 是 .NET 中用于表示异步操作的类。可以使用 Task.Run 方法来启动一个异步操作。示例如下:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// 启动一个异步操作
Task task = Task.Run(() =>
{
// 模拟耗时操作
System.Threading.Thread.Sleep(2000);
return 42;
});
// 等待任务完成并获取结果
int result = await task;
Console.WriteLine($"Result: {result}");
}
}
3.2.2ValueTask
ValueTask 是 .NET Core 2.1 引入的一种轻量级的异步操作表示方式,它是一个结构体。ValueTask 适用于那些大多数情况下可以同步完成的异步操作,以减少内存分配。示例如下:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
ValueTask valueTask = GetValueAsync();
int result = await valueTask;
Console.WriteLine($"Result: {result}");
}
static ValueTask GetValueAsync()
{
// 模拟可以同步完成的操作
return new ValueTask(42);
}
}
3.3 异步流
在 .NET 9 中,异步流得到了进一步的支持,主要通过 IAsyncEnumerable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
await foreach (var number in GenerateNumbersAsync())
{
Console.WriteLine(number);
}
}
static async IAsyncEnumerable GenerateNumbersAsync()
{
for (int i = 0; i < 5; i++)
{
// 模拟异步操作
await Task.Delay(1000);
yield return i;
}
}
}
四、.NET 9 新语言特性
4.1 模式匹配增强
在 .NET 9 中,模式匹配得到了进一步的增强,例如增强的类型模式和递归模式。
4.1.1 增强的类型模式
可以更方便地进行类型匹配和属性提取。示例如下:
using System;
class Program
{
static void PrintInfo(object obj)
{
if (obj is string str)
{
Console.WriteLine($"String length: {str.Length}");
}
else if (obj is int num)
{
Console.WriteLine($"Number: {num}");
}
}
static void Main()
{
PrintInfo("Hello");
PrintInfo(42);
}
}
4.1.2 递归模式
可以使用递归模式来匹配嵌套的数据结构。示例如下:
using System;
record Person(string Name, Person? Child);
class Program
{
static void PrintChildNames(Person person)
{
if (person is { Child: { Name: var childName } })
{
Console.WriteLine($"Child name: {childName}");
PrintChildNames(person.Child);
}
}
static void Main()
{
var person = new Person("Alice", new Person("Bob", new Person("Charlie", null)));
PrintChildNames(person);
}
}
4.2 可空引用类型改进
.NET 9 对可空引用类型进行了进一步的改进,使得在处理可能为 null 的引用类型时更加安全。可以通过在项目文件中设置
#nullable enable
using System;
class Program
{
static void Main()
{
string? nullableString = null;
if (nullableString is not null)
{
Console.WriteLine(nullableString.Length);
}
}
}
4.3 其他新特性
除了上述特性外,.NET 9 还引入了一些其他值得关注的语言特性,如记录类型的增强、静态抽象成员等。
4.3.1 记录类型的增强
记录类型在 .NET 9 中得到了进一步的优化,例如支持更灵活的构造函数和属性初始化。示例如下:
using System;
// 记录类型
record Person(string FirstName, string LastName)
{
public string FullName => $"{FirstName} {LastName}";
}
class Program
{
static void Main()
{
var person = new Person("John", "Doe");
Console.WriteLine(person.FullName);
}
}
4.3.2 静态抽象成员
静态抽象成员允许在接口中定义静态的抽象方法,要求实现接口的类型必须提供具体的实现。示例如下:
using System;
// 定义包含静态抽象成员的接口
public interface IStaticAbstract
{
static abstract int GetValue();
}
// 实现接口
public class MyClass : IStaticAbstract
{
public static int GetValue()
{
return 42;
}
}
class Program
{
static void Main()
{
int value = MyClass.GetValue();
Console.WriteLine($"Value: {value}");
}
}
总结
通过本篇的学习,你深入了解了 C# 的高级特性,包括委托、事件、Lambda 表达式、泛型、异步编程以及 .NET 9 带来的语言增强功能。这些知识能够帮助你编写更加灵活、高效和可维护的代码。
委托和事件为对象间的消息传递和回调机制提供了强大的支持,使得代码的解耦性得到提升。Lambda 表达式则以简洁的语法简化了委托的使用,尤其在 LINQ 查询等场景中发挥着重要作用。
泛型的引入让代码具有更好的复用性和类型安全性,通过泛型类、方法和接口,你可以编写出通用的代码逻辑,适用于不同的数据类型。同时,泛型约束进一步增强了泛型的灵活性和可靠性。
异步编程是现代软件开发中不可或缺的一部分,特别是在处理 I/O 密集型操作时,能够显著提高程序的性能和响应能力。Task 和 ValueTask 提供了不同场景下的异步操作解决方案,而异步流则让处理异步数据流变得更加方便。
.NET 9 的新语言特性,如模式匹配增强、可空引用类型改进、记录类型增强和静态抽象成员等,进一步提升了 C# 语言的表达能力和开发效率,使你能够更加优雅地解决复杂的编程问题。
在接下来的下篇中,我们将通过实际项目开发,综合运用这些知识,同时学习代码性能优化的技巧,进一步提升你的 .NET 9 开发能力。
实践案例:简单的异步文件读取与处理系统
下面我们通过一个简单的实践案例,综合运用本篇所学的异步编程、委托等知识,实现一个异步文件读取与处理系统。
using System;
using System.IO;
using System.Threading.Tasks;
// 定义一个委托,用于处理读取到的文件内容
delegate void FileContentProcessor(string content);
class FileProcessor
{
// 异步读取文件内容的方法
public async Task ReadFileAsync(string filePath, FileContentProcessor processor)
{
try
{
// 异步读取文件内容
string content = await File.ReadAllTextAsync(filePath);
// 调用委托处理文件内容
processor(content);
}
catch (Exception ex)
{
Console.WriteLine($"Error reading file: {ex.Message}");
}
}
}
class Program
{
static async Task Main()
{
string filePath = "test.txt";
FileProcessor fileProcessor = new FileProcessor();
// 定义处理文件内容的方法
FileContentProcessor processor = (content) =>
{
Console.WriteLine($"File content length: {content.Length}");
// 这里可以添加更多的处理逻辑,例如统计单词数量等
};
// 异步读取文件并处理内容
await fileProcessor.ReadFileAsync(filePath, processor);
}
}
代码解释:
- 委托定义:FileContentProcessor 委托用于封装处理文件内容的方法,它接受一个 string 类型的参数,表示读取到的文件内容。
- FileProcessor 类:该类包含一个异步方法 ReadFileAsync,用于异步读取指定路径的文件内容。读取完成后,调用传入的委托来处理文件内容。
- Main 方法:在 Main 方法中,我们创建了 FileProcessor 类的实例,并定义了一个 Lambda 表达式作为 FileContentProcessor 委托的实现。然后调用 ReadFileAsync 方法异步读取文件并处理内容。
练习题
为了帮助你巩固所学知识,以下是一些练习题:
委托与事件相关
- 定义一个委托 Calculator,用于封装两个整数的计算操作(如加法、减法等),并编写一个方法,该方法接受两个整数和一个 Calculator 委托实例,返回计算结果。
- 创建一个 Publisher 类,该类包含一个事件 DataChanged,并提供一个方法 ChangeData 用于触发该事件。创建一个 Subscriber 类,该类订阅 Publisher 类的 DataChanged 事件,并在事件处理方法中输出一条消息。
泛型相关
- 实现一个泛型栈类 GenericStack
,包含 Push、Pop 和 Peek 方法,分别用于入栈、出栈和查看栈顶元素。 - 编写一个泛型方法 Swap
,用于交换两个变量的值。
异步编程相关
- 编写一个异步方法 DownloadFileAsync,用于模拟从网络下载文件的操作,下载完成后返回文件的大小(以字节为单位)。
- 使用 IAsyncEnumerable
实现一个异步生成斐波那契数列的方法,并在 Main 方法中使用 await foreach 遍历该数列。
.NET 9 新特性相关
- 使用模式匹配增强功能,编写一个方法,根据传入的对象类型进行不同的处理。如果是 string 类型,输出其长度;如果是 int 类型,输出其平方;如果是其他类型,输出默认消息。
- 定义一个包含静态抽象成员的接口 IMathOperation,该接口包含一个静态抽象方法 Calculate,然后实现一个类 AddOperation 实现该接口,并提供 Calculate 方法的具体实现,用于计算两个整数的和。
通过完成这些练习题,你可以更加深入地理解和掌握本篇所学的知识,为后续的学习和实践打下坚实的基础。