1. 委托(Delegate)的本质与应用场景
委托是C#中实现回调机制的核心类型,本质上是一种类型安全的函数指针。与C++的函数指针不同,C#委托是面向对象且类型安全的,它定义了方法的签名,可以绑定到具有相同签名的任何方法。
1.1 委托类型声明与实例化
声明委托类型的标准语法:
csharp复制public delegate int MathOperation(int x, int y);
实例化委托的三种典型方式:
csharp复制// 方式1:直接绑定命名方法
MathOperation add = AddNumbers;
static int AddNumbers(int a, int b) => a + b;
// 方式2:通过构造函数绑定
MathOperation subtract = new MathOperation(SubtractNumbers);
// 方式3:使用匿名方法
MathOperation multiply = delegate(int a, int b) { return a * b; };
关键点:委托实例实际上是一个对象,它包含对方法的引用以及调用该方法的目标对象(如果是实例方法)。
1.2 多播委托与事件基础
委托的一个重要特性是支持多播(Multicast),即一个委托实例可以包含多个方法:
csharp复制MathOperation operations = AddNumbers;
operations += SubtractNumbers;
operations += multiply;
// 调用时会按添加顺序执行所有方法
int result = operations(10, 5);
多播委托的返回值通常是最后一个执行方法的返回值,前面方法的返回值会被丢弃。这在事件处理场景中特别有用,因为事件通常不需要返回值。
2. 事件(Event)的完整实现模式
事件是基于委托的发布-订阅机制,为对象提供通知能力。.NET中的事件遵循特定的设计模式。
2.1 标准事件实现
完整的事件实现应包含:
csharp复制public class TemperatureMonitor
{
// 1. 定义委托类型
public delegate void TemperatureChangedHandler(object sender, TemperatureChangedEventArgs e);
// 2. 定义基于委托的事件
public event TemperatureChangedHandler TemperatureChanged;
// 3. 定义事件参数类
public class TemperatureChangedEventArgs : EventArgs
{
public double OldTemperature { get; }
public double NewTemperature { get; }
public TemperatureChangedEventArgs(double oldTemp, double newTemp)
{
OldTemperature = oldTemp;
NewTemperature = newTemp;
}
}
// 4. 触发事件的受保护方法
protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e)
{
TemperatureChanged?.Invoke(this, e);
}
}
2.2 事件访问器的自定义
事件本质上是一对add/remove方法,我们可以自定义它们的行为:
csharp复制private EventHandler _listeners;
public event EventHandler TemperatureChanged
{
add
{
_listeners += value;
Console.WriteLine($"Added listener: {value.Method.Name}");
}
remove
{
_listeners -= value;
Console.WriteLine($"Removed listener: {value.Method.Name}");
}
}
3. 匿名函数与Lambda表达式的演进
从C# 1.0到现代版本,匿名函数的语法经历了显著演变。
3.1 匿名方法语法(C# 2.0)
csharp复制List<int> numbers = new List<int> { 1, 2, 3 };
List<int> evenNumbers = numbers.FindAll(
delegate(int num) { return num % 2 == 0; }
);
3.2 Lambda表达式语法(C# 3.0+)
csharp复制// 表达式Lambda
List<int> evenNumbers = numbers.FindAll(num => num % 2 == 0);
// 语句Lambda
numbers.ForEach(num => {
if(num % 2 == 0)
Console.WriteLine(num);
});
3.3 Lambda的类型推断
编译器能根据上下文推断Lambda参数类型:
csharp复制// 显式类型
Func<int, int> square = (int x) => x * x;
// 隐式类型
Func<int, int> square = x => x * x;
注意:当Lambda表达式转换为表达式树(Expression Tree)时,参数类型必须显式声明。
4. 高级Lambda特性与应用
4.1 闭包与捕获变量
Lambda可以捕获外部变量,形成闭包:
csharp复制int factor = 2;
Func<int, int> multiplier = n => n * factor;
Console.WriteLine(multiplier(3)); // 输出6
factor = 3;
Console.WriteLine(multiplier(3)); // 输出9
闭包捕获的是变量本身而非值,这可能导致一些意外行为:
csharp复制var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
action(); // 全部输出3,而非0,1,2
解决方法是在循环内创建局部变量副本:
csharp复制for (int i = 0; i < 3; i++)
{
int temp = i;
actions.Add(() => Console.WriteLine(temp));
}
4.2 异步Lambda
Lambda表达式可以与async/await一起使用:
csharp复制button.Click += async (sender, e) =>
{
await Task.Delay(1000);
MessageBox.Show("Clicked after delay");
};
5. List排序与高级比较操作
5.1 基本排序方法
csharp复制List<int> numbers = new List<int> { 3, 1, 4, 1, 5, 9 };
// 简单排序
numbers.Sort(); // 升序
numbers.Sort((a, b) => b.CompareTo(a)); // 降序
5.2 对象集合排序
csharp复制public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
List<Product> products = GetProducts();
// 按价格升序
products.Sort((p1, p2) => p1.Price.CompareTo(p2.Price));
// 使用LINQ创建新排序集合
var sortedProducts = products.OrderBy(p => p.Price)
.ThenByDescending(p => p.Name)
.ToList();
5.3 自定义比较器
csharp复制public class ProductComparer : IComparer<Product>
{
public int Compare(Product x, Product y)
{
int nameComparison = string.Compare(x.Name, y.Name, StringComparison.Ordinal);
if (nameComparison != 0)
return nameComparison;
return x.Price.CompareTo(y.Price);
}
}
products.Sort(new ProductComparer());
6. 协变与逆变深度解析
6.1 委托中的协变与逆变
csharp复制// 协变示例
Func<object> getObject = () => new object();
Func<string> getString = () => "hello";
// 可以将返回派生类型的方法赋值给返回基类型的委托
getObject = getString; // 协变
// 逆变示例
Action<string> actOnString = s => Console.WriteLine(s.Length);
Action<object> actOnObject = o => Console.WriteLine(o.ToString());
// 可以将接受基类型参数的方法赋值给接受派生类型参数的委托
actOnString = actOnObject; // 逆变
6.2 接口中的变体
csharp复制// 协变接口
interface IProducer<out T>
{
T Produce();
}
// 逆变接口
interface IConsumer<in T>
{
void Consume(T item);
}
// 使用示例
IProducer<string> stringProducer = new StringProducer();
IProducer<object> objectProducer = stringProducer; // 协变
IConsumer<object> objectConsumer = new ObjectConsumer();
IConsumer<string> stringConsumer = objectConsumer; // 逆变
6.3 泛型委托中的变体
.NET框架中的Func和Action委托已经定义了变体:
csharp复制// Func<out TResult> - 协变返回值
Func<string> stringFunc = () => "Hello";
Func<object> objectFunc = stringFunc;
// Action<in T> - 逆变参数
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction;
7. 性能优化与最佳实践
7.1 委托缓存
频繁创建的相同Lambda会导致重复分配:
csharp复制// 不推荐 - 每次调用都创建新委托
public void ProcessItems(IEnumerable<int> items)
{
items.Where(x => x > 5); // Lambda每次都会新建
}
// 推荐 - 缓存委托
private static readonly Predicate<int> GreaterThanFive = x => x > 5;
public void ProcessItems(IEnumerable<int> items)
{
items.Where(GreaterThanFive);
}
7.2 避免闭包中的意外捕获
csharp复制// 问题代码
for (int i = 0; i < 10; i++)
{
Task.Run(() => Console.WriteLine(i));
}
// 修正方案
for (int i = 0; i < 10; i++)
{
int current = i; // 创建局部副本
Task.Run(() => Console.WriteLine(current));
}
7.3 表达式树与编译执行
Lambda可以转换为表达式树或直接编译为委托:
csharp复制// 编译为委托
Func<int, int> square = x => x * x;
// 转换为表达式树
Expression<Func<int, int>> squareExpr = x => x * x;
var compiled = squareExpr.Compile(); // 转换为可执行委托
性能提示:频繁调用的Lambda表达式应考虑预编译,避免运行时重复解析。
