目 录CONTENT

文章目录

CSharp(四十九) 泛型委托详解

C# 泛型委托详解


一、什么是泛型委托?

1.1 先回忆普通委托的问题

普通委托只能绑定固定类型的方法。假如你要写一个"打印东西"的委托:

// 普通委托——类型写死了
delegate void Print(string message);   // 只能打印 string
delegate void PrintInt(int number);    // 只能打印 int
delegate void PrintDouble(double d);   // 只能打印 double

// 三个委托几乎一模一样,只是类型不同——代码重复!
Print p1 = msg => Console.WriteLine(msg);
PrintInt p2 = n => Console.WriteLine(n);
PrintDouble p3 = d => Console.WriteLine(d);

泛型委托就是给委托加上"类型参数",一个委托适配多种类型。

// 泛型委托——一个顶三个!
delegate void Print<T>(T item);

Print<string> p1 = msg => Console.WriteLine(msg);
Print<int> p2 = n => Console.WriteLine(n);
Print<double> p3 = d => Console.WriteLine(d);

p1("Hello");    // Hello
p2(123);        // 123
p3(3.14);       // 3.14

一句话:泛型委托 = 委托 + 泛型,让一个委托模板适配所有类型的参数。


1.2 生活类比

普通委托像是"只能装苹果的篮子",泛型委托像是"可以装任何水果的篮子,装的时候指定装什么"。

普通委托:    PrintString → 只能装 string
            PrintInt    → 只能装 int
            PrintDouble → 只能装 double

泛型委托:    Print<T> → 装的时候指定
            Print<string> → 装 string
            Print<int>    → 装 int
            Print<double> → 装 double

二、自定义泛型委托

2.1 基本定义

// 语法:delegate 返回值 委托名<类型参数>(参数列表);

// 无返回值的泛型委托
delegate void MyAction<T>(T item);

// 有返回值的泛型委托
delegate TResult MyFunc<T, TResult>(T input);

2.2 完整示例

using System;

// 定义泛型委托
delegate T Calculator<T>(T a, T b);

class Program
{
    // 匹配的方法(也都是泛型)
    static int AddInt(int a, int b) => a + b;
    static double AddDouble(double a, double b) => a + b;
    static string Concat(string a, string b) => a + b;

    static void Main()
    {
        Calculator<int> intCalc = AddInt;
        Console.WriteLine(intCalc(10, 20));       // 30

        Calculator<double> doubleCalc = AddDouble;
        Console.WriteLine(doubleCalc(3.5, 2.5));  // 6.0

        Calculator<string> stringCalc = Concat;
        Console.WriteLine(stringCalc("Hello", "World"));  // HelloWorld

        // 甚至可以用 Lambda
        Calculator<int> max = (a, b) => a > b ? a : b;
        Console.WriteLine(max(15, 27));           // 27
    }
}

2.3 多个类型参数

// 两个类型参数的泛型委托
delegate TResult Converter<TInput, TResult>(TInput input);

class Program
{
    static void Main()
    {
        // int → string
        Converter<int, string> intToStr = n => $"这是数字 {n}";
        Console.WriteLine(intToStr(42));  // 这是数字 42

        // string → int
        Converter<string, int> strToInt = s => int.Parse(s);
        Console.WriteLine(strToInt("123") + 1);  // 124

        // bool → string
        Converter<bool, string> boolToStr = b => b ? "是" : "否";
        Console.WriteLine(boolToStr(true));   // 是
        Console.WriteLine(boolToStr(false));  // 否
    }
}

2.4 泛型委托做方法参数

delegate bool Filter<T>(T item);

// 通用过滤方法——传入什么类型就筛选什么类型
static List<T> FilterList<T>(List<T> list, Filter<T> condition)
{
    List<T> result = new List<T>();
    foreach (T item in list)
    {
        if (condition(item))
            result.Add(item);
    }
    return result;
}

// 使用
List<int> numbers = new List<int> { 1, 15, 7, 23, 8, 42, 3 };
List<string> names = new List<string> { "Alice", "Bob", "Charlie", "David" };

var bigNumbers = FilterList(numbers, n => n > 10);
var longNames = FilterList(names, n => n.Length > 4);

Console.WriteLine($"大于10的数字: {string.Join(", ", bigNumbers)}");  // 15, 23, 42
Console.WriteLine($"长名字: {string.Join(", ", longNames)}");          // Alice, Charlie, David

三、内置泛型委托(Action、Func、Predicate)

C# 已经帮你定义好了最常用的泛型委托,99% 的情况下不需要自己定义

3.1 Action —— 无返回值

// Action 家族:可以有 0~16 个参数,没有返回值

Action                sayHello = () => Console.WriteLine("你好!");
Action<string>        print = s => Console.WriteLine(s);
Action<string, int>   repeat = (s, n) =>
{
    for (int i = 0; i < n; i++)
        Console.Write(s);
    Console.WriteLine();
};
Action<int, int, int> sum3 = (a, b, c) => Console.WriteLine(a + b + c);

sayHello();              // 你好!
print("泛型委托");        // 泛型委托
repeat("⭐", 3);          // ⭐⭐⭐
sum3(1, 2, 3);           // 6
// Action 的实际使用场景
List<string> names = new List<string> { "张三", "李四", "王五" };

// List 的 ForEach 方法接收的就是 Action<T>
names.ForEach(name => Console.WriteLine($"学生: {name}"));

// 输出:
// 学生: 张三
// 学生: 李四
// 学生: 王五

3.2 Func —— 有返回值

// Func 家族:最后一个类型参数是返回值类型

Func<int>                    rollDice = () => new Random().Next(1, 7);
Func<int, int>               square = x => x * x;
Func<int, int, int>          add = (a, b) => a + b;
Func<string, string, bool>   startsWith = (s, prefix) => s.StartsWith(prefix);
Func<int, int, int, double> avg = (a, b, c) => (a + b + c) / 3.0;

Console.WriteLine(rollDice());            // 比如: 4
Console.WriteLine(square(9));             // 81
Console.WriteLine(add(15, 27));           // 42
Console.WriteLine(startsWith("Hello", "He"));  // True
Console.WriteLine(avg(80, 90, 100));      // 90.0

Func 参数的命名不是固定的,关键看有几个参数:

Func<int, bool>         // 参数 int   → 返回 bool
Func<string, int, bool> // 参数 string, int → 返回 bool
Func<double>            // 无参数    → 返回 double

记忆技巧:Func 的最后一个类型参数永远是返回值类型,前面的都是参数类型。

3.3 Predicate —— 返回 bool

// Predicate<T> 等价于 Func<T, bool>,专门用于做判断

Predicate<int> isPositive = n => n > 0;
Predicate<string> isLong = s => s.Length > 5;
Predicate<object> isNull = obj => obj == null;

Console.WriteLine(isPositive(-5));    // False
Console.WriteLine(isLong("Hello"));   // False
Console.WriteLine(isNull(null));      // True

Predicate 主要用在 List 的 Find、FindAll 等方法:

List<int> scores = new List<int> { 45, 78, 92, 60, 55, 88, 39 };

// Find:找第一个满足条件
int firstPass = scores.Find(s => s >= 60);
Console.WriteLine($"第一个及格的: {firstPass}");  // 78

// FindAll:找所有满足条件
List<int> allPassed = scores.FindAll(s => s >= 60);
Console.WriteLine($"全部及格的: {string.Join(", ", allPassed)}");  // 78, 92, 60, 88

// FindIndex:找索引
int index = scores.FindIndex(s => s == 92);
Console.WriteLine($"92分的索引: {index}");  // 2

// RemoveAll:删除所有满足条件的
scores.RemoveAll(s => s < 60);
Console.WriteLine($"删除不及格后: {string.Join(", ", scores)}");  // 78, 92, 60, 88

四、内置泛型委托速查表

委托类型 参数个数 返回值 什么时候用 示例
Action 0 执行一个操作 Action act = () => ...
Action<T> 1 处理一个东西 Action<string> print = s => ...
Action<T1,T2> 2 处理两个东西 Action<int,int> = (a,b) => ...
Action<T1~T16> 1~16 多参数操作
Func<TResult> 0 1 得到一个值 Func<int> r = () => 42
Func<T,TResult> 1 1 转换/处理返回 Func<int,int> sq = x => x*x
Func<T1,T2,TResult> 2 1 两入一出 Func<int,int,int> add
Func<T1~T16,TResult> 1~16 1 多入一出
Predicate<T> 1 bool 判断条件 Predicate<int> p = x => x>0

五、实战示例——泛型委托的综合运用

5.1 示例:通用数据处理器

using System;
using System.Collections.Generic;

class Program
{
    // 通用处理器——三个委托参数:筛选、转换、输出
    static List<TResult> ProcessData<TInput, TResult>(
        List<TInput> data,
        Predicate<TInput> filter,      // 筛选条件
        Func<TInput, TResult> convert,  // 转换规则
        Action<TResult> output)         // 输出动作
    {
        List<TResult> results = new List<TResult>();

        foreach (TInput item in data)
        {
            if (filter(item))
            {
                TResult converted = convert(item);
                output(converted);
                results.Add(converted);
            }
        }

        return results;
    }

    static void Main()
    {
        // 原始数据
        List<int> scores = new List<int> { 45, 78, 92, 60, 55, 88, 39 };
        List<string> names = new List<string> { "ZhangSan", "LiSi", "WangWu", "ZhaoLiu" };

        // 场景一:处理分数——筛选及格,转成等级,打印
        Console.WriteLine("===== 分数处理 =====");
        var grades = ProcessData(
            scores,
            s => s >= 60,                              // 筛选:及格
            s => s >= 90 ? "A" : s >= 80 ? "B" : "C",  // 转换:分数→等级
            grade => Console.Write($"{grade} "));
        Console.WriteLine();

        // 场景二:处理名字——筛选长度>5,转成大写,打印
        Console.WriteLine("\n===== 名字处理 =====");
        var processed = ProcessData(
            names,
            n => n.Length > 5,           // 筛选:名字长于5
            n => n.ToUpper(),            // 转换:转大写
            n => Console.Write($"{n} "));
        Console.WriteLine();
    }
}

输出:

===== 分数处理 =====
B A C B
===== 名字处理 =====
ZHANGSAN WANGWU ZHAOLIU

5.2 示例:回调系统

using System;

class Program
{
    // 异步操作,完成后回调
    static void DoWorkAsync<T>(Func<T> work, Action<T> onSuccess, Action<string> onError)
    {
        try
        {
            Console.WriteLine("工作中...");
            T result = work();
            onSuccess(result);   // 成功时回调
        }
        catch (Exception ex)
        {
            onError(ex.Message); // 失败时回调
        }
    }

    static void Main()
    {
        // 成功的情况
        DoWorkAsync(
            work: () => { return 42; },
            onSuccess: result => Console.WriteLine($"成功!结果: {result}"),
            onError: err => Console.WriteLine($"失败: {err}")
        );

        // 失败的情况
        DoWorkAsync(
            work: () => { throw new Exception("网络超时"); },
            onSuccess: r => Console.WriteLine($"结果: {r}"),
            onError: err => Console.WriteLine($"失败: {err}")
        );
    }
}

输出:

工作中...
成功!结果: 42
工作中...
失败: 网络超时

5.3 示例:链式操作

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        // LINQ 的方法很多就是用 Func 和 Predicate 实现的
        // Where   → 接收 Func<T, bool>(也就是 Predicate<T>)
        // Select  → 接收 Func<T, TResult>
        // ForEach → 接收 Action<T>

        var result = numbers
            .Where(n => n % 2 == 0)       // Predicate<int>: 筛选偶数
            .Select(n => n * n)            // Func<int, int>:  转成平方
            .OrderByDescending(n => n);    // Func<int, int>:  降序排列

        Console.WriteLine("偶数平方降序:");
        result.ToList().ForEach(n => Console.Write($"{n} "));
        // 输出: 100 64 36 16 4
    }
}

5.4 完整实战:简易事件总线

using System;
using System.Collections.Generic;

class EventBus
{
    // 用一个泛型字典存储事件处理器
    // key 是事件类型,value 是处理器委托列表
    private Dictionary<Type, Delegate> _handlers
        = new Dictionary<Type, Delegate>();

    // 注册事件处理器(订阅)
    public void Subscribe<T>(Action<T> handler)
    {
        Type eventType = typeof(T);
        if (_handlers.ContainsKey(eventType))
        {
            _handlers[eventType] = Delegate.Combine(_handlers[eventType], handler);
        }
        else
        {
            _handlers[eventType] = handler;
        }
        Console.WriteLine($"订阅 {eventType.Name} 事件成功");
    }

    // 发布事件
    public void Publish<T>(T eventData)
    {
        Type eventType = typeof(T);
        if (_handlers.ContainsKey(eventType))
        {
            Action<T> handler = (Action<T>)_handlers[eventType];
            handler?.Invoke(eventData);
        }
    }
}

// 事件数据类型
class UserLoginEvent
{
    public string Username;
    public DateTime LoginTime;
}

class OrderCreatedEvent
{
    public int OrderId;
    public double Amount;
}

class Program
{
    static void Main()
    {
        var bus = new EventBus();

        // 订阅用户登录事件
        bus.Subscribe<UserLoginEvent>(e =>
        {
            Console.WriteLine($"📝 [日志] 用户 {e.Username} 在 {e.LoginTime:HH:mm:ss} 登录");
        });

        bus.Subscribe<UserLoginEvent>(e =>
        {
            Console.WriteLine($"🔔 [通知] 欢迎回来,{e.Username}!");
        });

        // 订阅订单事件
        bus.Subscribe<OrderCreatedEvent>(e =>
        {
            Console.WriteLine($"💰 [财务] 新订单 #{e.OrderId},金额 {e.Amount} 元");
        });

        Console.WriteLine("\n===== 发布事件 =====");

        // 发布事件
        bus.Publish(new UserLoginEvent { Username = "张三", LoginTime = DateTime.Now });
        bus.Publish(new OrderCreatedEvent { OrderId = 10086, Amount = 299.5 });
    }
}

输出:

订阅 UserLoginEvent 事件成功
订阅 UserLoginEvent 事件成功
订阅 OrderCreatedEvent 事件成功

===== 发布事件 =====
📝 [日志] 用户 张三 在 14:30:25 登录
🔔 [通知] 欢迎回来,张三!
💰 [财务] 新订单 #10086,金额 299.5 元

六、泛型委托与普通委托的对比

6.1 代码量对比

// ===== 不用泛型委托 =====
delegate int IntOperation(int a, int b);
delegate double DoubleOperation(double a, double b);
delegate string StringOperation(string a, string b);
// ... 每加一种类型就加一个委托

// ===== 用泛型委托 =====
delegate T Operation<T>(T a, T b);  // 一个顶所有

// 或者直接用内置的 Func<T, T, T>
Func<int, int, int> intOp = (a, b) => a + b;
Func<double, double, double> doubleOp = (a, b) => a + b;
Func<string, string, string> stringOp = (a, b) => a + b;

6.2 对比表

特性 普通委托 泛型委托
类型灵活度 写死类型 类型参数化,调用时指定
代码量 每种类型一个委托 一个委托所有类型
维护成本 高(加类型要加委托)
类型安全 安全 安全(编译期检查)
代表 delegate void MyAction(string s) delegate void MyAction<T>(T s)

七、泛型委托的约束

泛型委托和普通泛型一样,可以加约束来限制类型参数:

// 约束 T 必须是引用类型
delegate void ReferenceAction<T>(T item) where T : class;

// 约束 T 必须是值类型
delegate void ValueAction<T>(T item) where T : struct;

// 约束 T 必须有无参构造函数
delegate T Creator<T>() where T : new();

// 约束 T 必须继承自某个类或实现某接口
delegate void AnimalAction<T>(T animal) where T : Animal;

// 多约束
delegate T Converter<T, U>(U input)
    where T : class, new()
    where U : struct;

使用示例:

class Animal { }
class Dog : Animal { public Dog() { } }

// T 必须是 Animal 的子类,且有无参构造
delegate T AnimalCreator<T>() where T : Animal, new();

AnimalCreator<Dog> makeDog = () => new Dog();
Dog dog = makeDog();

// AnimalCreator<int> makeInt = () => 5;  // ❌ 错误!int 不继承 Animal

八、匿名方法与 Lambda 中的泛型委托

8.1 Lambda 自动适配泛型委托

// 同一个 Lambda 可以用在不同类型的委托上
var lambda = (int x) => x * 2;

Func<int, int> f1 = lambda;     // 没问题
// Predicate<int> p1 = lambda;  // ❌ 返回 int,不是 bool

8.2 泛型方法返回泛型委托

// 创建一个返回泛型委托的方法
static Func<T, bool> CreateFilter<T>(T threshold) where T : IComparable<T>
{
    return item => item.CompareTo(threshold) > 0;
}

// 使用
var greaterThan5 = CreateFilter(5);
Console.WriteLine(greaterThan5(10));  // True
Console.WriteLine(greaterThan5(3));   // False

var greaterThanH = CreateFilter("H");
Console.WriteLine(greaterThanH("Z")); // True
Console.WriteLine(greaterThanH("A")); // False

九、常见易错点(避坑指南)

坑1:用自定义泛型委托而不用内置的

// ❌ 重复造轮子
delegate void MyVoidAction<T>(T item);
delegate TResult MyFunc<T, TResult>(T input);
delegate bool MyPredicate<T>(T item);

// ✅ 直接用内置的
Action<string> print = s => Console.WriteLine(s);
Func<int, string> convert = n => n.ToString();
Predicate<int> check = n => n > 0;

坑2:Func 的参数顺序搞反

// Func 的最后一个参数永远是返回值!

// ❌ 错误理解
// Func<int, string, bool> —— "int 和 bool 参数,返回 string"  ← 错的!

// ✅ 正确理解
Func<int, string, bool>  // 参数: int, string  →  返回: bool

坑3:泛型委托和协变/逆变

// 内置的 Action 和 Func 已经标了 in 和 out,支持协变逆变
Action<object> printObj = obj => Console.WriteLine(obj);
Action<string> printStr = printObj;  // ✅ 逆变!(Action<in T>)

Func<Dog> makeDog = () => new Dog();
Func<Animal> makeAnimal = makeDog;   // ✅ 协变!(Func<out TResult>)

// 自己定义的泛型委托不自动支持协变/逆变,除非手动加 in/out
delegate T MyFunc<out T>();          // 手动加 out 才支持协变
delegate void MyAction<in T>(T t);   // 手动加 in 才支持逆变

坑4:找不到就用 Func 和 Action 组合

// 如果找不到合适的委托,就用 Func 和 Action 组合

// 需要 3 个参数 + 返回 bool?
Func<int, string, double, bool> myCheck = (x, s, d) =>
{
    return x > 0 && s.Length > 3 && d < 100;
};

// 需要 4 个参数 + 无返回值?
Action<int, int, int, int> myAction = (a, b, c, d) =>
{
    Console.WriteLine(a + b + c + d);
};

坑5:Lambda 和局部函数的选择

// 当 Lambda 太长时,考虑用局部函数代替

// ❌ Lambda 太长了
Func<int, int, int> calc = (a, b) =>
{
    if (a > 100) return a;
    if (b < 0) return 0;
    int sum = a + b;
    sum *= 2;
    return sum;
};

// ✅ 用局部函数更清晰
int Calc(int a, int b)
{
    if (a > 100) return a;
    if (b < 0) return 0;
    int sum = a + b;
    sum *= 2;
    return sum;
}
Func<int, int, int> calc2 = Calc;

十、总结

泛型委托三步决策法

我需要一个委托 →

1. 有返回值吗?
   ├─ 无 → 用 Action 系列
   │      Action          (无参数)
   │      Action<T>       (1个参数)
   │      Action<T1,T2>   (2个参数)
   │      ...
   │
   ├─ 返回 bool → 用 Predicate<T> 或 Func<T, bool>
   │
   └─ 返回其他 → 用 Func 系列
          Func<TResult>              (无参数)
          Func<T, TResult>           (1个参数)
          Func<T1, T2, TResult>      (2个参数)
          ...

2. 参数超过 16 个?
   └─ 用自定义泛型委托

3. 需要约束?
   └─ 自定义泛型委托 + where 约束

速查表

你想要的 用什么
执行一个操作,不要返回值 Action / Action<T>
得到一个值 Func<T>
转换一个值 Func<T, TResult>
判断一个值 Predicate<T>Func<T, bool>
两个参数操作 Func<T1, T2, TResult>Action<T1, T2>
需要约束类型参数 自定义泛型委托 +where

记忆口诀

委托不加泛型累,每种类型都得写
加上泛型变灵活,一套模板全解决

Action 干活不要回报
Func 办事必有结果(最后一个参数是返回值)
Predicate 专门做判断(返回 bool)

内置三兄弟全搞定,自己定义已没必要

一句话总结:泛型委托就是让委托支持类型参数,一个定义适配所有类型。C# 内置了三大家族——Action(干活不要回报)、Func(办事有结果)、Predicate(专门做判断),99% 的场景不需要自己定义泛型委托,直接用它们就对了。

0
博主关闭了当前页面的评论