C# 委托使用详解

总目录


前言

在C#中,委托(Delegate) 是一种类型安全的函数指针机制,它允许我们将方法作为参数传递给其他方法,或者将方法存储在变量中。委托在 C# 中有广泛的应用,特别是在事件处理、异步编程和回调机制中。本文将详细介绍 C# 中委托的定义、使用方法、高级特性以及最佳实践。


一、什么是委托

1. 定义

  • 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。
  • 委托是一种特殊的类,它封装了一个或多个具有相同签名(返回类型和参数列表)的方法。它允许我们将方法作为参数传递给其他方法,或者将方法存储在变量中。 委托可以看作是方法的“蓝图”,它定义了方法的签名(返回类型和参数列表),但不包含方法体。
  • 委托是一种类型安全的函数指针,用于封装对方法的引用。它允许将方法作为参数传递、存储在变量中,或通过多播机制调用多个方法。

2. 特点

  • 类型安全:委托的签名(参数列表和返回类型)必须与目标方法签名完全匹配。
  • 面向对象:委托本质上是继承自 System.MulticastDelegate 的类,支持多态和反射操作。
  • 动态调用:运行时根据条件动态选择要调用的方法。
  • 多播能力:支持多个方法的链式调用
  • 异步支持:通过BeginInvoke/EndInvoke实现异步调用

3. 委托定义语法

委托是一种引用类型,它指向具有特定参数列表和返回类型的方法。委托的定义语法如下:

public delegate ReturnType DelegateName(Parameters);
  • ReturnType:委托方法的返回类型。
  • Parameters:委托方法的参数列表。
  • DelegateName:委托的名称。

二、如何使用委托

1. 使用步骤

1)定义委托

  • 定义委托的方式类似于定义方法
    • 相较于定义方法,定义委托需要使用 delegate 关键字,且没有方法体。
  • 委托本质上也是类,因此可定义的位置与类相同。
    • 类可以定义在哪里,委托就可定义在哪里。
public delegate int MathOperationDelegate(int a, int b);

2)实例化委托

可以通过以下几种方式实例化委托:

  • new 实例化:通过new 创建委托实例
  • 直接赋值 :直接赋值给某个方法
  • 匿名方法 :使用匿名方法
  • Lambda 表达式:使用 Lambda 表达式
方式1:new 实例化
// 实例化委托:new 实例化
MathOperationDelegate mathOperation = new MathOperationDelegate(Add);

通过new 创建委托实例,必须传入一个方法作为参数,否则会报错
在这里插入图片描述

// 1. 定义委托
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
    	// 3.实例化委托
        MathOperationDelegate mathOperation = new MathOperationDelegate(Add);
    }

	// 2.定义委托对应的方法
	// 注意:该方法的签名必须与定义的委托签名保持一致
    public static int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}
方式2:直接赋值

C#2.0 提供一个更简单的给委托创建实例的方式,那就是直接赋值。

 // 实例化委托:直接赋值,将Add 方法赋值给定义的委托
MathOperationDelegate mathOperation = Add;
// 1. 定义委托
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
        // 3.实例化委托:直接赋值,将Add 方法赋值给定义的委托
        MathOperationDelegate mathOperation = Add;
    }

    // 2.定义委托对应的方法
    // 注意:该方法的签名必须与定义的委托签名保持一致
    public static int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}
方式3:匿名方法

从C# 2.0开始,可以使用匿名方法来创建委托实例。匿名方法是在创建委托实例时定义的方法体,不需要单独命名。

// 实例化委托: 使用匿名方法,可在不定义命名方法的情况下直接编写方法体
MathOperationDelegate mathOperation = delegate (int num1, int num2) 
{ 
      return num1 + num2; 
};
// 1. 定义委托
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
        // 2.实例化委托: 使用匿名方法,可在不定义命名方法的情况下直接编写方法体
        MathOperationDelegate mathOperation = delegate (int num1, int num2) 
        { 
            return num1 + num2; 
        };
    }
}
方式4:Lambda 表达式

从C# 3.0开始,可以使用Lambda表达式来创建委托实例。Lambda表达式是一种更简洁的匿名方法语法。

 // 实例化委托: 使用 Lambda 表达式
 MathOperationDelegate mathOperation = (x, y) => x + y;
// 1. 定义委托
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
        // 2.实例化委托: 使用 Lambda 表达式
        MathOperationDelegate mathOperation = (x, y) => x + y;
    }
}

3)使用委托

通过调用委托,就可以间接的调用委托上对应的方法。

方式1:调用委托变量

传入对应的参数,像调用方法一样调用委托。

mathOperation(4, 5)
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
        MathOperationDelegate mathOperation = (x, y) => x + y;
        Console.WriteLine(mathOperation(4, 5)); //输出:9
    }
}
方式2:Invoke 调用

使用Invoke方法,调用委托。使用Invoke调用委托,如果定义的委托没有参数,则invoke也没有参数,如果定义的委托没有返回值,则invoke也没有返回值。

mathOperation.Invoke(4, 5)
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
        MathOperationDelegate mathOperation = (x, y) => x + y;
        Console.WriteLine(mathOperation.Invoke(4, 5)); //输出:9
    }
}

2. 示例

1)完整示例代码

// 1. 定义委托
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    // 2.定义委托对应的方法
    // 注意:该方法的签名必须与定义的委托签名保持一致
    public static int Add(int num1, int num2)
    {
        return num1 + num2;
    }

    static void Main()
    {
        // 3.实例化委托

        // 实例化方式 1
        MathOperationDelegate mathOperation1 = new MathOperationDelegate(Add);

        // 实例化方式 2
        MathOperationDelegate mathOperation2 = Add;

        // 实例化方式 3
        MathOperationDelegate mathOperation3 = delegate (int x, int y)
        {
            return x + y;
        };

        // 实例化方式 4
        MathOperationDelegate mathOperation4 = (x, y) => x + y;

        //4. 调用委托

        // 调用方式 1
        var reselt1 = mathOperation1(4, 5);
        Console.WriteLine(reselt1); //输出:9

        // 调用方式 2
        var reselt2 = mathOperation4.Invoke(4, 5);
        Console.WriteLine(reselt2); //输出:9
    }
}

2)委托基本使用示例

// 1. 定义委托
public delegate int MathOperationDelegate(int a, int b);

class Program
{
    static void Main()
    {
        // 2. 实例化委托
        MathOperationDelegate mathOperation = (x, y) => x + y;

        // 3. 调用委托
        Console.WriteLine(mathOperation.Invoke(4, 5)); //输出:9
    }
}

3. 为什么使用委托

  • 解耦代码:分离方法定义与调用逻辑,例如事件处理、异步回调。
  • 实现回调机制:委托提供了灵活的回调机制,可以在方法执行完成时触发特定的操作。
  • 提高代码复用性:通过委托,可以将通用的处理逻辑提取出来,减少重复代码。
  • 灵活性和扩展性:委托允许你在运行时动态地指定要调用的方法,使得代码更加灵活和易于扩展。
  • 异步编程支持:委托支持异步编程模型,使得你可以轻松地编写异步操作。
  • 支持事件驱动:通过事件封装委托,实现发布-订阅模式

三、委托的特性

1. 多播委托

1)基本信息

委托可以关联多个方法,形成多播委托。多播委托通过 +=-= 操作符来添加或移除关联方法,形成一个委托链。当调用这个委托链时,所有关联的方法按添加的顺序依次执行。

2)多播委托特点

  • 委托链:多播委托可以看作是一个委托链,其中每个节点代表一个方法。
  • 操作符支持:通过 += 操作符添加方法,通过 -= 操作符移除方法。
  • 调用顺序:当调用一个多播委托时,会按照它们被添加的顺序依次调用每个方法。
  • 返回值:如果委托有返回值,只有最后一个方法的返回值会被保留,其他返回值会被丢弃。
  • 异常处理:如果其中一个方法抛出异常,则后续的方法将不会被调用,除非使用特殊的方式处理异常(如捕获异常并继续执行)。

3)使用示例

为了更好地理解多播委托的工作原理,我们来看一个具体的示例。

首先,定义一个简单的委托类型,并编写几个方法:

using System;

// 定义一个委托类型
public delegate void MathOperation(int a, int b);

public class Calculator
{
    public void Add(int a, int b)
    {
        Console.WriteLine($"Add: {a} + {b} = {a + b}");
    }

    public void Subtract(int a, int b)
    {
        Console.WriteLine($"Subtract: {a} - {b} = {a - b}");
    }

    public void Multiply(int a, int b)
    {
        Console.WriteLine($"Multiply: {a} * {b} = {a * b}");
    }
}

接下来,我们将这些方法绑定到一个多播委托实例中,并依次调用它们:

public class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();

        // 创建委托实例并绑定方法
        MathOperation operation = calculator.Add;
        operation += calculator.Subtract;
        operation += calculator.Multiply;	//添加方法
		operation -= calculator.Multiply;	//移除方法
        // 调用多播委托
        operation(5, 3);
    }
}

当你运行上述代码时,输出将是:

Add: 5 + 3 = 8
Subtract: 5 - 3 = 2

可以看到,方法 AddSubtract 都被依次调用了,由于Multiply方法使用-=移除,所以没被调用 。

4)获取委托链中的方法

有时你可能需要获取多播委托链中的所有方法,以便进行进一步的处理或调试。可以通过 GetInvocationList() 方法来实现这一点。

public class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();

        // 创建委托实例并绑定方法
        MathOperation operation = calculator.Add;
        operation += calculator.Subtract;
        operation += calculator.Multiply;

        // 获取委托链中的所有方法
        Delegate[] delegates = operation.GetInvocationList();
        foreach (MathOperation method in delegates)
        {
            Console.WriteLine($"Calling method: {method.Method.Name}");
            method(5, 3);
        }
    }
}

输出结果

Calling method: Add
Add: 5 + 3 = 8
Calling method: Subtract
Subtract: 5 - 3 = 2
Calling method: Multiply
Multiply: 5 * 3 = 15

5)返回值处理

多播委托的返回值是最后一个被调用的方法的返回值。如果你希望获取所有方法的返回值,可以通过遍历委托链中的方法逐一调用并收集结果。

假设我们有一个带返回值的委托类型:

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

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Subtract(int a, int b)
    {
        return a - b;
    }

    public int Multiply(int a, int b)
    {
        return a * b;
    }
}

public class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();

        // 创建委托实例并绑定方法
        MathOperationWithReturn operation = calculator.Add;
        operation += calculator.Subtract;
        operation += calculator.Multiply;

        // 调用多播委托并获取最后一个方法的返回值
        int result = operation(5, 3);
        Console.WriteLine($"Last method's result: {result}");

        // 获取所有方法的返回值
        Delegate[] delegates = operation.GetInvocationList();
        foreach (MathOperationWithReturn method in delegates)
        {
            int tempResult = method(5, 3);
            Console.WriteLine($"{method.Method.Name} returned: {tempResult}");
        }
    }
}

输出结果

Last method's result: 15
Add returned: 8
Subtract returned: 2
Multiply returned: 15

6)异常处理

如果多播委托链中的某个方法抛出异常,默认情况下后续的方法将不会被调用。为了避免这种情况,可以在调用每个方法时捕获异常并继续执行。

public class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();

        // 创建委托实例并绑定方法
        MathOperation operation = calculator.Add;
        operation += ThrowException; // 这个方法会抛出异常
        operation += calculator.Multiply;

        // 调用多播委托并处理异常
        InvokeAll(operation, 5, 3);
    }

    public static void ThrowException(int a, int b)
    {
        throw new InvalidOperationException("An exception occurred!");
    }

    public static void InvokeAll(MathOperation operation, int a, int b)
    {
        Delegate[] delegates = operation.GetInvocationList();
        foreach (MathOperation method in delegates)
        {
            try
            {
                Console.WriteLine($"Calling method: {method.Method.Name}");
                method(a, b);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Caught exception in {method.Method.Name}: {ex.Message}");
            }
        }
    }
}

输出结果

Calling method: Add
Add: 5 + 3 = 8
Calling method: ThrowException
Caught exception in ThrowException: An exception occurred!
Calling method: Multiply
Multiply: 5 * 3 = 15

2. 泛型委托

1)自定义泛型委托

// 1. 定义委托
public delegate T MathOperationDelegate<T>(T a, T b);

class Program
{
    static void Main()
    {
        // 2. 实例化委托
        MathOperationDelegate<int> mathOperation1 = (x, y) => x + y;
        MathOperationDelegate<double> mathOperation2 = (x, y) => x - y;

        // 3. 调用委托
        Console.WriteLine(mathOperation1.Invoke(4, 5)); //输出:9
        Console.WriteLine(mathOperation2.Invoke(45.5, 25.1)); //输出:20.4
    }
}

建议:C# 提供了一些内置的泛型委托类型,如 Func<T>Action<T>Predicate<T>,这些内置委托已经涵盖了大多数常见的场景。除非有特殊需求,否则建议直接使用这些内置委托,而不是重新定义新的委托类型。

2)内置通用委托

C# 提供了一些内置的泛型委托类型,如 Func<T>Action<T>Predicate<T> ,简化了委托的定义和使用。

对比表格
特性Action<T> 系列Func<T> 系列Predicate<T>
定义表示一个没有返回值的委托表示一个有返回值的委托表示一个返回bool 的委托
泛型参数数量0到16个输入参数,
无返回值
0到16个输入参数,
最后一个表示返回值且只有一个
固定1个输入参数,
返回布尔值
返回值类型void可以是任意类型bool
使用场景执行某些操作但不需要返回结果执行某些计算或处理并返回结果判断某个条件是否满足
常见用法处理事件、执行副作用操作计算、查询、转换等需要返回结果的操作过滤集合元素、验证条件
Action<T> 系列
  • 定义Action<T> 是一个不带返回值的委托类型,可以接受0到16个输入参数。
  • 泛型参数Action 本身表示无参数的委托,Action<T> 表示接受一个参数的委托,依此类推,最多可以接受16个参数。
  • 返回值void,即没有返回值。
  • 典型应用
    • 执行某些操作,如日志记录、文件写入等。
    • 处理事件回调函数。
public class Program
{
    static void Main()
    {
        //实例化方式 1
        Action action = new Action(Show);
        action();
        //实例化方式 2
        action = Show;
        action();
        //实例化方式 3
        action = delegate () { Console.WriteLine("hello"); };
        action();

        //实例化方式 4

        // Action 无参数
        Action log = () => Console.WriteLine("Logging...");
        log();

        // Action<T> 单个参数
        Action<string> printMessage = message => Console.WriteLine(message);
        printMessage("Hello, World!");

        // Action<T1, T2> 多个参数
        Action<int, int> addAndPrint = (a, b) => Console.WriteLine(a + b);
        addAndPrint(5, 3);
    }

    public static void Show()
    {
        Console.WriteLine("hello world!");
    }
}
Func<T> 系列
  • 定义Func<T> 是一个带有返回值的委托类型,可以接受0到16个输入参数,并且必须有一个返回值。
  • 泛型参数Func<TResult> 表示无参数但有返回值的委托,Func<T, TResult> 表示接受一个参数并返回结果的委托,依此类推,最多可以接受16个参数。
  • 返回值:最后一个泛型参数指定返回值的类型。
  • 典型应用
    • 执行计算、查询、转换等需要返回结果的操作。
    • 数据处理和映射。
public class Program
{
    static void Main()
    {
        // Func<TResult> 无参数但有返回值
        Func<int> getRandomNumber = () => new Random().Next(1, 100);
        Console.WriteLine(getRandomNumber());

        // Func<T, TResult> 单个参数
        Func<int, int> square = x => x * x;
        Console.WriteLine(square(5));

        // Func<T1, T2, TResult> 多个参数
        Func<int, int, int> add = (a, b) => a + b;
        Console.WriteLine(add(5, 3));
    }
}
Predicate<T>
  • 定义Predicate<T> 是一个返回布尔值的委托类型,专门用于判断某个条件是否满足。它只接受一个输入参数。
  • 泛型参数:固定为一个参数类型。
  • 返回值bool,用于表示条件是否成立。
  • 典型应用
    • 集合过滤、元素验证等场景。
    • 条件判断。
public class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

        // Predicate<T> 判断数字是否大于3
        Predicate<int> isGreaterThanThree = n => n > 3;

        // 使用 Find 方法查找第一个符合条件的元素
        int result = numbers.Find(isGreaterThanThree);
        Console.WriteLine(result); // 输出: 4

        // 使用 Where 方法过滤所有符合条件的元素
        var filteredNumbers = numbers.Where(n => isGreaterThanThree(n));
        foreach (var num in filteredNumbers)
        {
            Console.WriteLine(num); // 输出: 4, 5
        }
    }
}
Predicate<int> isGreaterThanThree = n => n > 3;

// 使用 Find 方法查找第一个符合条件的元素
int result = numbers.Find(isGreaterThanThree);
//等效于
int result = numbers.Find(x => x > 3);


var filteredNumbers = numbers.Where(n => isGreaterThanThree(n));
//等效于
 var filteredNumbers = numbers.Where(x => x > 3);

Predicate<T> 在 此处FindWhere 中的作用就是筛选出符合条件的数据。当数据符合条件,返回true,将符合条件的数据返回或加入到新的序列中,返回新的序列。

3)小结

  • Action<T>表示一个没有返回值的委托,Action<T>相当于是对所有 无返回值委托的进一步包装
  • Func<T>表示一个有返回值的委托,Func<T>相当于是对所有 有返回值委托的进一步包装
  • Action<T>Func<T> 内置委托基本已经涵盖了所有的委托使用场景,除非有特殊需求,否则建议直接使用这些内置委托,而不是重新定义新的委托类型。
  • Func<T>表示一个返回bool 的委托,Func<T>相当于是对所有 返回bool委托的进一步包装

3. 异步委托

委托支持异步调用,可以使用 BeginInvokeEndInvoke 方法进行异步操作。

  • BeginInvoke 开启一个线程去执行委托
  • BeginInvokeEndInvoke 方法 仅 NetFamework支持 ,NetCore中更推荐 async/await
using System;
using System.Reflection;

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

public class Calculator
{
    public int Add(int a, int b)
    {
        Console.WriteLine(MethodBase.GetCurrentMethod().Name);
        return a + b;
    }

    public int Subtract(int a, int b)
    {
        Console.WriteLine(MethodBase.GetCurrentMethod().Name);
        return a - b;
    }
}
class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();
        MathOperation operation = calculator.Add;

        // 异步调用委托
        IAsyncResult result = operation.BeginInvoke(5, 3, null, null);

        // 在主线程继续执行其他操作
        Console.WriteLine("Doing other work...");

        // 等待异步调用完成并获取结果
        int sum = operation.EndInvoke(result);
        Console.WriteLine($"Sum: {sum}"); // 输出: Sum: 8
    }
}

运行结果:

Doing other work...
Add
Sum: 8

如果是使用Invoke 方法同步执行:

class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();
        MathOperation operation = calculator.Add;

        // 同步调用委托
        int sum = operation.Invoke(5, 3);

        // 继续执行其他操作
        Console.WriteLine("Doing other work...");

        // 输出结果
        Console.WriteLine($"Sum: {sum}"); // 输出: Sum: 8
    }
}

结果如下

Add
Doing other work...
Sum: 8

四、委托的最佳实践

1. 优先使用内置委托

优先使用内置委托,减少冗余代码,提升可维护性。 C# 提供了 ActionFuncPredicate类型,用于表示常见的委托类型。在大多数情况下,建议使用 ActionFunc 类型,而不是自定义委托类型。

2. 使用 Lambda 表达式

Lambda 表达式提供了简洁的语法,用于表示匿名函数。在大多数情况下,建议使用 Lambda 表达式,而不是匿名委托或命名方法。

3. 避免过度使用委托

虽然委托非常灵活,但在某些情况下,过度使用委托可能会导致代码难以阅读和维护。在设计代码时,应权衡委托的灵活性和代码的可读性。

5. 避免复杂的委托链

虽然委托链功能强大,但过于复杂的委托链会使代码难以维护。尽量保持委托链简单明了。

4. 注意线程安全性

在多线程环境中使用委托时,需要注意线程安全性问题。确保在访问共享资源时使用适当的同步机制。

// 错误的委托调用方式
public class UnsafeInvoker
{
    public Action Callback;

    public void DoWork()
    {
        if (Callback != null) // ❌ 非原子操作
        {
            Callback();       // ❌ 可能已被置空
        }
    }
}

// 正确方式
public void SafeDoWork()
{
    var localCopy = Callback;
    if (localCopy != null)
    {
        localCopy();
    }
}

5. 合理选择委托或事件

需要解耦通知机制时用事件,动态调用方法时用委托。

6. 注意闭包变量生命周期

避免意外捕获外部变量导致内存泄漏。

五、应用场景

1. 应用场景概览

  • 事件处理:委托常用于事件处理机制,使得对象可以订阅和触发事件。
  • 回调函数:委托可以用来实现回调函数,使得一个方法可以在另一个方法完成后被调用。
  • 异步编程:委托支持异步编程模型,使得你可以轻松地编写异步操作。
  • 多播委托:委托可以封装多个方法,并按顺序调用这些方法。

2. 应用场景示例

示例1:事件处理

委托是C#中事件处理的基础,委托常用于事件处理机制。以下是一个简单的事件处理示例:

public class Button
{
    // 定义一个委托类型
    public delegate void ClickEventHandler(object sender, EventArgs e);

    // 定义一个事件
    public event ClickEventHandler Click;

    // 触发事件
    public void OnClick()
    {
        Click?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void Main()
    {
        Button button = new Button();

        // 订阅事件
        button.Click += (sender, e) =>
        {
            Console.WriteLine("Button clicked!");
        };

        // 触发事件
        button.OnClick(); // 输出: Button clicked!
    }
}

示例2:回调函数

委托可以用来实现回调函数,使得一个方法可以在另一个方法完成后被调用。

public class TaskManager
{
    public void ExecuteTask(Action callback)
    {
        Console.WriteLine("Executing task...");
        // 模拟任务执行
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine("Task completed.");

        // 调用回调函数
        callback();
    }
}

class Program
{
    static void Main()
    {
        TaskManager taskManager = new TaskManager();

        // 定义回调函数
        Action callback = () => Console.WriteLine("Callback executed!");

        // 执行任务并传递回调函数
        taskManager.ExecuteTask(callback);
    }
}

输出结果:

Executing task...
Task completed.
Callback executed!

示例3: 回调函数

委托可以用作回调函数,当某个操作完成时,调用委托来通知调用者。例如:

public delegate void MyDelegate(string message);

public class DataLoader
{
    public void LoadData(MyDelegate callback)
    {
        // 模拟数据加载
        System.Threading.Thread.Sleep(1000);
        callback("Data loaded successfully!");
    }
}
public class Program
{
    public static void Main()
    {
        DataLoader loader = new DataLoader();
        loader.LoadData(DisplayMessage);
    }
    public static void DisplayMessage(string message)
    {
        Console.WriteLine(message);
    }
}

示例4:异步编程

委托支持异步编程模型,使得你可以轻松地编写异步操作。

public class AsyncOperation
{
    public void PerformOperation(Func<int, int, int> operation, int a, int b)
    {
        Console.WriteLine("Starting asynchronous operation...");
        var result = operation.BeginInvoke(a, b, null, null);

        // 在主线程继续执行其他操作
        Console.WriteLine("Doing other work...");

        // 获取异步操作的结果
        int sum = operation.EndInvoke(result);
        Console.WriteLine($"Result: {sum}");
    }
}

class Program
{
    static void Main()
    {
        AsyncOperation asyncOperation = new AsyncOperation();

        // 定义异步操作
        Func<int, int, int> addFunc = (x, y) =>
        {
            Console.WriteLine("Add Operation");
            return x + y;
        };

        // 执行异步操作
        asyncOperation.PerformOperation(addFunc, 5, 3);
    }
}

运行结果:

Starting asynchronous operation...
Doing other work...
Add Operation
Result: 8

示例5:异步流水线处理

虽然委托支持使用BeginInvoke异步调用,但在现代C#中,推荐使用 asyncawait 来实现异步操作。这种方式更加直观和易于维护。

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task PipelineProcessingAsync()
    {
        // 定义下载数据的操作
        Func<string, Task<string>> download = async url => {
            await Task.Delay(100); // 模拟网络延迟
            return $"Data from {url}";
        };

        // 定义处理数据的操作
        Func<string, Task<string>> process = async data => {
            await Task.Delay(50); // 模拟处理时间
            return data.ToUpper();
        };

        // 定义保存结果的操作
        Func<string, Task> save = async result => {
            await Task.Delay(20); // 模拟保存时间
            Console.WriteLine($"Saved: {result}");
        };

        // 构建处理流水线
        async Task ProcessPipeline(string url)
        {
            var downloadedData = await download(url);
            var processedData = await process(downloadedData);
            await save(processedData);
        }

        // 执行处理流水线
        await ProcessPipeline("http://example.com");
    }
    
	// NetFamework 下的写法
    public static void Main(string[] args)
    {    	
        PipelineProcessingAsync().GetAwaiter().GetResult();
        //输出:Saved: DATA FROM HTTP://EXAMPLE.COM
    }
}

NetCore下的写法

    public static async void Main(string[] args)
    {
        await PipelineProcessingAsync();
        //输出:Saved: DATA FROM HTTP://EXAMPLE.COM
    }

示例6:动态方法选择器

public class CommandDispatcher
{
    private readonly Dictionary<string, Action<string>> _handlers 
        = new Dictionary<string, Action<string>>();

    public void RegisterHandler(string command, Action<string> handler)
    {
        _handlers[command] = handler;
    }

    public void Execute(string command, string parameter)
    {
        if (_handlers.TryGetValue(command, out var handler))
        {
            handler(parameter);
        }
        else
        {
            throw new InvalidOperationException($"未知命令: {command}");
        }
    }
}

示例7:策略模式实现

public class PaymentProcessor
{
    private Func<Order, PaymentResult> _paymentStrategy;

    public void SetPaymentStrategy(Func<Order, PaymentResult> strategy)
    {
        _paymentStrategy = strategy;
    }

    public PaymentResult Process(Order order)
    {
        return _paymentStrategy?.Invoke(order) 
            ?? throw new InvalidOperationException("未设置支付策略");
    }
}

// 使用示例
processor.SetPaymentStrategy(CreditCardStrategy);
var result = processor.Process(order);

示例8:动态方法调用

通过反射动态绑定委托,适用于插件化架构:

using System;
using System.Reflection;

public class Utility
{
    // Encrypt 方法接受一个字符串参数并返回加密后的字符串
    public static string Encrypt(string input)
    {
        // 简单的加密逻辑(实际应用中应使用更安全的加密算法)
        char[] chars = input.ToCharArray();
        for (int i = 0; i < chars.Length; i++)
        {
            chars[i] = (char)(chars[i] + 1); // 将每个字符编码值加1
        }
        return new string(chars);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        // 获取 Utility 类中的 Encrypt 方法
        MethodInfo method = typeof(Utility).GetMethod("Encrypt", BindingFlags.Public | BindingFlags.Static);

        if (method == null)
        {
            Console.WriteLine("未能找到 Encrypt 方法。");
            return;
        }

        // 使用反射创建 Func<string, string> 委托实例
        Delegate delegateInstance = Delegate.CreateDelegate(typeof(Func<string, string>), method);

        if (delegateInstance == null)
        {
            Console.WriteLine("未能创建委托实例。");
            return;
        }

        // 将 delegateInstance 转换为 Func<string, string>
        Func<string, string> encryptFunc = (Func<string, string>)delegateInstance;

        // 调用委托实例
        string originalText = "Hello, World!";
        string encryptedText = encryptFunc(originalText);

        Console.WriteLine($"原始文本: {originalText}");
        Console.WriteLine($"加密后的文本: {encryptedText}");
    }
}

示例9:表达式树

// 传统匿名方法
MathOperation sqrt = delegate(int x) { return Math.Sqrt(x); };

// Lambda表达式
MathOperation sqrt = x => Math.Sqrt(x);

// 表达式树(编译时转换为委托)
Expression<Func<int, double>> expr = x => Math.Sqrt(x);
var compiled = expr.Compile();
public class Program
{
    public static void Main()
    {
        Expression<Func<int, double>> expr = x => Math.Sqrt(x);
        var compiled = expr.Compile();
        Console.WriteLine(compiled.Invoke(16)); //输出:4
    }
}

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
Microsoft Docs: 事件
Microsoft Docs: 异步编程
Microsoft Docs: 委托文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/985024.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

axure11安装教程包含下载、安装、汉化、授权(附安装包)图文详细教程

文章目录 前言一、axure11安装包下载二、axure11安装教程1.启动安装程序2.安装向导界面3.安装协议协议页面2.选择安装位置3.开始安装4.完成安装 三、axure11汉化教程1.axure11汉化包2.axure11汉化设置 四、axure11授权教程1.打开axure112.设置使用方式3.输入许可证号4.axure11安…

如何使用Opentelemetry+jaeger对Go与Java项目实现分布式链路追踪

本文介绍![如何使用Opentelemetryjaeger实现分布式链路追踪] 关于opentelemetry的介绍可以看下面的文章 https://blog.csdn.net/qq_62368250/article/details/143516314本文中相关图片以及源代码地址 https://github.com/wuchenyanghaoshuai/others/blob/main/step39/README.…

【数据分享】2001-2024年我国逐年植被净初级生产力(NPP)数据

植被净初级生产力&#xff08;Net Primary Productivity&#xff0c;NPP&#xff09;是生态学中的一个重要概念&#xff0c;表示单位面积植被在特定时间内吸收的净光合有机物&#xff0c;是衡量生态系统中植物通过光合作用所产生的有机物质减去植物呼吸作用消耗的有机物质的量&…

靶场(七)---靶场精做小白思考

启程&#xff1a; 先扫一遍全端口发现&#xff0c;有很多tcp端口全部被关闭了&#xff0c;于是我又去看看他们的udp端口&#xff0c;发现也是半死不活的样子&#xff0c;那没办法只能把udp端口当作备选方案&#xff08;其实这个udp什么用都没有就是关闭的状态不用关&#xff0…

Linux开发工具----vim

目录 Linux编辑器-vim使用 1. vim的基本概念 正常/普通/命令模式(Normal mode) 插入模式(Insert mode) 底行模式(last line mode) 2. vim的基本操作 3. vim正常模式命令集 4. vim底行模式命令集 5. vim操作总结 (本篇文章相当于vim常用命令字典) Linux编辑器-vim使用 我们先来看…

【设计模式】设计模式的分类与组织

文章目录 前言一、设计模式的分类1. 目的准则2. 范围准则 二、设计模式的细分1.创建型模式的细分2.结构型模式的细分3.行为型模式的细分 三、设计模式的关联结论 前言 在软件开发中&#xff0c;设计模式是一种解决特定问题的最佳实践。由于设计模式种类繁多&#xff0c;理解它…

Vue3实战学习(Element-Plus常用组件的使用(输入框、下拉框、单选框多选框、el-image图片))(上)(5)

目录 一、Vue3工程环境配置、项目基础脚手架搭建、Vue3基础语法、Vue3集成Element-Plus的详细教程。(博客链接如下) 二、Element-Plus常用组件使用。 &#xff08;1&#xff09;el-input。(input输入框) <1>正常状态的el-input。 <2>el-input的disable状态。 <3…

AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘

AttributeError: module backend_interagg has no attribute FigureCanvas 这个错误通常是由于 Matplotlib 的后端配置问题引起的。具体来说&#xff0c;Matplotlib 在尝试加载某个后端时&#xff0c;发现该后端模块中缺少必要的属性&#xff08;如 FigureCanvas&#xff09;&a…

网络安全基础与应用习题 网络安全基础答案

1.列出并简要给出SSH的定义。 正确答案&#xff1a; 答&#xff1a;6.10传输层协议&#xff1a;提供服务器身份验证、数据保密性和数据完整性&#xff0c;并具有前向保密性&#xff08;即&#xff0c;如果在一个会话期间密钥被破坏&#xff0c;则知识不会影响早期会话的安全性&…

文件上传复现

1、什么是文件上传漏洞&#xff1f; 答&#xff1a;文件上传漏洞是指攻击者通过上传恶意文件到服务器、从而执行任意代码、获取系统权限或者破坏系统安全的漏洞、常见于允许用户上传文件的Web应用程序中。 2. 文件上传漏洞形成原因 未验证文件类型&#xff1a;未对上传文件的…

【网络安全工程】任务12:网络安全设备

目录 一、防火墙​ 1、作用​ 2、配置方式​ 3、存在的漏洞​ 二、入侵检测系统&#xff08;IDS&#xff09;和入侵防御系统&#xff08;IPS&#xff09;​ 1、作用​ 2、配置方式​ 3、存在的漏洞​ 三、防病毒网关​ ​1、作用​ 2、配置方式​ 3、存在的漏洞​ …

Docker容器与宿主机目录映射深度解析

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、Docker容器与宿主机目录映射基础原理 在深入了解如何查询 Docker 容器目录在宿主机的映射目录之前&#xff0c;有必要先明晰其背后的基础原理。Docker 容器通过挂载&#xff08;mount&#xff09;机制将宿主机的…

DeepSeek+Maxkb+Ollama+Docker搭建一个AI问答系统

DeepSeekMaxkbOllamaDocker搭建一个AI问答系统 文章目录 DeepSeekMaxkbOllamaDocker搭建一个AI问答系统前言一、创建同一内网的网络二、拉取两个镜像三、启动Ollama以及调试Maxkb4.Maxkb创建一个应用并建立知识库5、应用效果总结 前言 我觉得只要是使用Docker技术&#xff0c;…

决策树,Laplace 剪枝与感知机

1.决策树 决策树是一种用于分类任务的监督学习算法。它基于特征的划分来做出决策&#xff0c;每个节点表示一个特征&#xff0c;每条分支代表该特征的可能取值&#xff0c;每个叶子节点代表分类结果。 用通俗的话来说&#xff0c;决策树就像一个**"如果……那么……&quo…

使用DeepSeek+蓝耘快速设计网页简易版《我的世界》小游戏

前言&#xff1a;如今&#xff0c;借助先进的人工智能模型与便捷的云平台&#xff0c;即便是新手开发者&#xff0c;也能开启创意游戏的设计之旅。DeepSeek 作为前沿的人工智能模型&#xff0c;具备强大的功能与潜力&#xff0c;而蓝耘智算云平台则为其提供了稳定高效的运行环境…

标准卷积(Standard Convolution)

标准卷积的基础操作图解&#xff1a; 卷积之后尺寸公式&#xff1a; 输入尺寸&#xff1a;WH卷积核尺寸&#xff1a;Fw​Fh​填充大小&#xff1a;P步长&#xff1a;S 输出尺寸 WoutHout可以通过以下公式计算&#xff1a; 其中[x]表示向下取整。 实例&#xff1a; 输入图像…

使用 Elastic-Agent 或 Beats 将 Journald 中的 syslog 和 auth 日志导入 Elastic Stack

作者&#xff1a;来自 Elastic TiagoQueiroz 我们在 Elastic 一直努力将更多 Linux 发行版添加到我们的支持矩阵中&#xff0c;现在 Elastic-Agent 和 Beats 已正式支持 Debian 12&#xff01; 本文演示了我们正在开发的功能&#xff0c;以支持使用 Journald 存储系统和身份验…

ArcGIS Pro中字段的新建方法与应用

一、引言 在地理信息系统&#xff08;GIS&#xff09;的数据管理和分析过程中&#xff0c;字段操作起着至关重要的作用。 无论是进行地图制作、空间分析还是数据统计&#xff0c;字段都是承载属性信息的基本单元。 ArcGIS Pro作为一款功能强大的GIS软件&#xff0c;为用户提…

企业如何选择网站模版快速建站?

企业想拥有一个专业和功能齐全的官网事关重要。然而&#xff0c;对于新手用户&#xff0c;从基础开始创建网站可能是一件较为复杂和麻烦的过程。今天的文章从零开始给介绍一下如何搭建网站。 一、先确定建站的主题 用户在开始建站前明确自己建站的目的、是为了宣传网站、还是销…

Phi-4-multimodal:图、文、音频统一的多模态大模型架构、训练方法、数据细节

Phi-4-Multimodal 是一种参数高效的多模态模型&#xff0c;通过 LoRA 适配器和模式特定路由器实现文本、视觉和语音/音频的无缝集成。训练过程包括多阶段优化&#xff0c;确保在不同模式和任务上的性能&#xff0c;数据来源多样&#xff0c;覆盖高质量网络和合成数据。它的设计…