位置: 文档库 > C#(.NET) > C#委托使用详解(Delegates)

C#委托使用详解(Delegates)

GenesisDragon 上传于 2021-08-13 20:21

C#委托使用详解(Delegates)》

在C#编程中,委托(Delegates)是一种强大的类型安全函数指针机制,允许开发者将方法作为参数传递、存储在数据结构中或通过事件进行回调。作为面向对象与函数式编程的桥梁,委托在事件处理、异步编程、回调模式等场景中发挥着核心作用。本文将从基础概念到高级应用,系统解析委托的语法、类型、多播委托、匿名方法与Lambda表达式,并结合实际案例展示其强大功能。

一、委托基础概念

委托是一种引用方法的类型,类似于C++中的函数指针,但具有类型安全和面向对象的特性。委托定义指定了方法的返回类型和参数列表,任何符合该签名的方法都可以赋值给委托变量。

1.1 委托定义与实例化

委托通过delegate关键字定义,语法如下:

public delegate int MathOperation(int x, int y);

上述代码定义了一个名为MathOperation的委托,可引用任何接收两个int参数并返回int的方法。实例化委托时,需传入匹配的方法:

public class Calculator {
    public static int Add(int a, int b) => a + b;
    public static int Multiply(int a, int b) => a * b;
}

// 实例化委托
MathOperation op = Calculator.Add;
int result = op(3, 4); // 返回7

1.2 委托与方法的松耦合

委托的核心价值在于解耦调用者与被调用方法。例如,一个排序算法可通过委托接收比较逻辑,无需关心具体实现:

public delegate int CompareDelegate(int a, int b);

public static void BubbleSort(int[] array, CompareDelegate compare) {
    for (int i = 0; i  0) {
                (array[j], array[j + 1]) = (array[j + 1], array[j]);
            }
        }
    }
}

// 使用示例
int[] numbers = { 5, 2, 8, 1 };
BubbleSort(numbers, (a, b) => a.CompareTo(b)); // 升序
BubbleSort(numbers, (a, b) => b.CompareTo(a)); // 降序

二、委托类型与多播委托

委托分为单播委托(引用单个方法)和多播委托(引用多个方法)。多播委托通过+-运算符组合委托实例,调用时会按顺序执行所有方法。

2.1 多播委托的创建与调用

public delegate void NotificationDelegate(string message);

public class MessageService {
    public static void SendEmail(string msg) => Console.WriteLine($"Email: {msg}");
    public static void SendSMS(string msg) => Console.WriteLine($"SMS: {msg}");
}

// 创建多播委托
NotificationDelegate notify = MessageService.SendEmail;
notify += MessageService.SendSMS;

// 调用多播委托
notify("Hello, World!"); // 依次输出Email和SMS内容

2.2 多播委托的返回值处理

多播委托执行时,仅最后一个方法的返回值会被保留。若需收集所有返回值,需手动遍历委托的调用列表(GetInvocationList()):

public delegate int CalculationDelegate(int x);

public static int Square(int x) => x * x;
public static int Cube(int x) => x * x * x;

var calculations = Square + Cube;
var results = new List();
foreach (var calc in calculations.GetInvocationList().Cast()) {
    results.Add(calc(3)); // 分别计算9和27
}

三、匿名方法与Lambda表达式

C# 2.0引入匿名方法,允许直接在委托实例化时内联定义方法体。C# 3.0进一步推出Lambda表达式,以更简洁的语法实现相同功能。

3.1 匿名方法示例

public delegate void LoggerDelegate(string log);

LoggerDelegate logger = delegate(string message) {
    Console.WriteLine($"[LOG] {DateTime.Now}: {message}");
};

logger("System started"); // 输出带时间戳的日志

3.2 Lambda表达式语法

Lambda表达式使用=>运算符简化匿名方法

// 表达式Lambda(无语句块)
Func add = (x, y) => x + y;

// 语句Lambda(含语句块)
Action greet = name => {
    string greeting = $"Hello, {name}!";
    Console.WriteLine(greeting);
};

3.3 闭包与变量捕获

Lambda表达式可捕获外部变量(形成闭包),但需注意变量生命周期:

Action[] actions = new Action[3];
for (int i = 0; i  Console.WriteLine(i); // 捕获循环变量i
}
foreach (var action in actions) {
    action(); // 输出3次"3"(因i在循环结束后为3)
}

// 修正方法:创建局部变量
for (int i = 0; i  Console.WriteLine(temp);
}

四、委托在事件中的核心应用

事件是委托的典型应用场景,通过发布-订阅模式实现对象间的解耦。.NET中的事件基于委托实现,遵循event关键字修饰的委托字段规范。

4.1 自定义事件实现

public class TemperatureMonitor {
    public event Action TemperatureChanged;

    private double _currentTemp;
    public double CurrentTemp {
        get => _currentTemp;
        set {
            if (_currentTemp != value) {
                _currentTemp = value;
                TemperatureChanged?.Invoke(_currentTemp); // 触发事件
            }
        }
    }
}

public class AlertSystem {
    public void OnTemperatureChanged(double temp) {
        if (temp > 30) Console.WriteLine("WARNING: High temperature!");
    }
}

// 使用示例
var monitor = new TemperatureMonitor();
var alert = new AlertSystem();
monitor.TemperatureChanged += alert.OnTemperatureChanged;
monitor.CurrentTemp = 35; // 触发警告

4.2 事件处理程序的标准模式

遵循以下规范可提升代码可维护性:

  • 事件委托类型通常继承EventHandlerEventHandler
  • 自定义事件参数类需继承EventArgs
public class TemperatureChangedEventArgs : EventArgs {
    public double OldTemp { get; }
    public double NewTemp { get; }
    public TemperatureChangedEventArgs(double old, double newTemp) {
        OldTemp = old;
        NewTemp = newTemp;
    }
}

public class AdvancedMonitor {
    public event EventHandler TempChanged;

    private double _temp;
    public double Temperature {
        get => _temp;
        set {
            if (_temp != value) {
                var old = _temp;
                _temp = value;
                TempChanged?.Invoke(this, new TemperatureChangedEventArgs(old, value));
            }
        }
    }
}

// 使用示例
var monitor = new AdvancedMonitor();
monitor.TempChanged += (sender, e) => {
    Console.WriteLine($"Temp changed from {e.OldTemp} to {e.NewTemp}");
};
monitor.Temperature = 25;

五、委托的高级应用场景

5.1 异步编程中的回调

委托可用于实现异步操作的回调机制,例如模拟长时间运行的任务:

public delegate void AsyncOperationCompleted(string result);

public class AsyncProcessor {
    public void StartLongRunningTask(AsyncOperationCompleted callback) {
        Task.Run(() => {
            Thread.Sleep(2000); // 模拟耗时操作
            callback("Operation completed!");
        });
    }
}

// 使用示例
var processor = new AsyncProcessor();
processor.StartLongRunningTask(result => Console.WriteLine(result));

5.2 策略模式实现

委托可替代接口实现策略模式,提供更灵活的行为定制:

public delegate decimal DiscountStrategy(decimal originalPrice);

public class ShoppingCart {
    private DiscountStrategy _discountStrategy;
    public void SetDiscountStrategy(DiscountStrategy strategy) => 
        _discountStrategy = strategy;
    
    public decimal CalculateTotal(decimal price) => 
        _discountStrategy?.Invoke(price) ?? price;
}

// 使用示例
var cart = new ShoppingCart();
cart.SetDiscountStrategy(p => p * 0.9m); // 10%折扣
Console.WriteLine(cart.CalculateTotal(100)); // 输出90

5.3 LINQ中的委托应用

LINQ查询大量依赖委托(如FuncPredicate)实现过滤、排序等操作:

var numbers = new List { 1, 2, 3, 4, 5 };

// 使用Predicate委托的旧语法
var evens = numbers.FindAll(delegate(int x) { return x % 2 == 0; });

// 使用Lambda表达式的现代语法
var odds = numbers.Where(x => x % 2 != 0).ToList();

六、委托的性能与最佳实践

6.1 性能考量

委托调用涉及少量开销(约相当于虚方法调用),在性能敏感场景中需注意:

  • 避免在频繁调用的循环中创建委托实例
  • 多播委托的调用开销随方法数量线性增长

6.2 最佳实践总结

  1. 明确委托用途:区分单播与多播场景
  2. 优先使用Lambda:除非需要复用方法体
  3. 注意线程安全:多播委托的调用顺序不保证线程安全
  4. 避免内存泄漏:及时取消事件订阅(-=操作)
  5. 使用标准事件模式:遵循EventHandler规范

七、委托与接口的对比选择

在以下场景中,委托通常比接口更合适:

场景 委托优势
需要动态改变行为 运行时可替换方法实现
单一方法接口 委托语法更简洁
事件通知机制 内置多播支持

而接口更适合需要多个方法或维护对象状态的场景。

结语

委托作为C#语言的核心特性之一,通过类型安全的方式实现了函数指针的全部功能,并在事件处理、异步编程、策略模式等领域展现出独特优势。从基础的单播委托到复杂的多播闭包,从匿名方法到简洁的Lambda表达式,掌握委托的使用技巧能显著提升代码的灵活性和可维护性。在实际开发中,合理运用委托可使代码更符合开闭原则,同时降低组件间的耦合度。

关键词:C#委托、多播委托、Lambda表达式、事件处理、匿名方法、闭包、策略模式、异步回调、类型安全函数指针、EventHandler

简介:本文全面解析C#委托机制,涵盖基础定义、多播委托、匿名方法与Lambda表达式、事件模型、高级应用场景及性能优化。通过代码示例展示委托在解耦、回调、策略模式中的核心作用,对比委托与接口的适用场景,总结最佳实践帮助开发者高效利用这一特性。