- 委托
- 委托最大的好处
- 委托的第二个作用就是可以多播
- 泛型委托
- 预定义的委托
- *`EventHandler` 委托:*
- 事件
- 事件就是一种特殊的委托
- 举个例子
- 微软为我们提供了标准事件模式
委托
这是一个简单的委托样例
class TODO {public static void Main(String[] args) {Cal aa = new Cal(Add);Console.WriteLine(aa(2, 3));}public delegate int Cal(int a, int b);private static int Add(int a, int b) {return a + b;}private static int Dec(int a, int b) {return a - b;}}
如此简单的应用就是函数指针的意思
委托就是我给你原材料,你给我我要的成品,至于你拿我原材料干啥,我不是深究。
于是,有一个问题,为什么我不直接调用Add
或者Dec
函数呢?
这就是
委托最大的好处
变量分离,将不变的封装起来,隔离变化,例子如下:
class TODO {public static void Main(String[] args) {test(Add, 2, 3, 4);}public delegate int Cal(int a, int b);internal static void test(Cal cc, int a, int b, int c) {int x = 0;int y = 0;if (a > b) {if (a > c) {x = a;} else {if (b > c) {y = b;} else {y = c;}}} else {if (b > c) {x = b;} else {x = c;if (a > b) {y = a;} else {y = b;}}}int result = cc(x, y);Console.WriteLine(result);}private static int Add(int a, int b) {return a + b;}private static int Dec(int a, int b) {return a - b;}}
我有一个方案test
他的一切都是固定下来的,但是他的计算方法是多变的,因为我们可以用委托来改变传入的方法
于是我们在不改变test
的情况下让他有了更多的功能
委托的第二个作用就是可以多播
就是一个委托实例可以添加多个函数,当然也能删除,要是都删了,委托new的实例就是null
代码如下:
class TODO {public static void Main(String[] args) {Foo f = new Foo(f1);f.Invoke();Console.WriteLine("-------------------");f += f2;f += f3;f();Console.WriteLine("-------------------");f -= f1;f -= f3;f += f1;f();Console.WriteLine("-------------------");f -= f1;f -= f2;if(f == null) { Console.WriteLine("null"); } else {Console.WriteLine("unnull");}Console.WriteLine("-------------------");f?.Invoke();Console.WriteLine("-------------------");}internal delegate void Foo();private static void f1() { Console.WriteLine("f1"); }private static void f2() { Console.WriteLine("f2"); }private static void f3() { Console.WriteLine("f3"); }
}
在C#
中,f?.Invoke();
是一个使用null
条件运算符(也称为Elvis
运算符)的表达式。这个表达式用于安全地调用一个可能为null
的委托(delegate
)f
。
这里的 ?.
运算符检查 f
是否为 null
。如果 f
不是 null
,则 Invoke
方法会被调用;如果 f
是 null
,则整个表达式不会有任何效果(即不会抛出异常,也不会执行任何操作)。
这种语法特别有用,因为它可以避免在尝试调用一个null
引用的方法时抛出 NullReferenceException
异常。
总之就是判断是否为null
,不是就正常执行,是就无事发生。
打印结果为:
f1
-------------------
f1
f2
f3
-------------------
f2
f1
-------------------
null
-------------------
-------------------
注意
委托的多播最后的返回值是最后加入的函数的返回值
class TODO {public static void Main(String[] args) {Foo f = new Foo(f1);Console.WriteLine(f(2));f += f2;Console.WriteLine(f(3));}delegate string Foo(int x);static string f1(int i) {return (i+1).ToString();}static string f2(int i) {return (i+2).ToString();}}
运行结果为:
3
5
泛型委托
delegate T NumberChanger<T>(T n);
class TODO {public static void Main(String[] args) {//声明实例TODO doit = new TODO();//声明委托实例,确定模板的类型为intRule<int> rule = new Rule<int>(doit.MsRule);//通过实例调用其成员Console.WriteLine(doit.PickOne<int>(1, 2, rule));}//模板函数 接收两个类型为T的参数和一个Rule<T>类型的委托作为参数,//然后调用这个委托并返回其结果private T PickOne<T>(T a, T b, Rule<T> rule) {return rule(a, b);}//接收两个整数参数,并返回较大的那个整数。public int MsRule(int a, int b) {return a > b ? a : b;}
}//泛型委托Rule<T>,它接收两个类型为T的参数,并返回一个类型为T的结果。
delegate T Rule<T>(T a, T b);
注意:上述代码没有使用静态static
,故要通过实例调度,即非静态方法是依附于类的实例,必须通过实例来调用
预定义的委托
然后我们再想,既然都用泛型委托了,其实对于声明一个委托来说,委托的名字叫什么并不重要,重要的是确定他的返回值类型和参数列表
所以每次要声明一个委托模板就显得有点繁琐,故:
在.NET
中,委托是一种类型安全的函数指针,它允许将方法作为参数传递或赋值给变量。委托定义了一种方法的签名,即它接受的参数类型和返回类型。任何与委托签名匹配的方法都可以赋值给该委托的变量。
.NET
框架提供了多种预定义的委托类型,用于处理常见的场景。以下是一些常用的.NET
委托:
Action
委托:
Action
委托用于表示没有返回值(即返回类型为 void
)的方法。Action
有多个重载版本,可以接受不同数量的参数,例如:
Action
:无参数。
Action<T>
:接受一个参数。
Action<T1, T2>
:接受两个参数。
以此类推,直到 Action<T1, T2, ..., T16>
。
Func
委托:
Func
委托用于表示有返回值的方法。与 Action
类似,Func
也有多个重载版本,每个版本都指定了返回类型和一个或多个输入参数类型,例如:
Func<TResult>
:无参数,返回 TResult
类型的结果。
Func<T, TResult>
:接受一个类型为 T
的参数,返回 TResult
类型的结果。
Func<T1, T2, TResult>
:接受两个参数,返回 TResult
类型的结果。
以此类推,直到 Func<T1, T2, ..., T16, TResult>
。
Predicate
委托:
Predicate<T>
委托用于定义返回 bool
类型的方法,它接受一个类型为 T
的参数。这通常用于需要条件测试的场景,例如数组或集合中的元素筛选。
Comparison
委托:
Comparison<T>
委托用于定义比较两个类型为 T
的对象的方法,并返回一个整数来表示它们的相对顺序。这常用于排序操作。
EventHandler
委托:
EventHandler
和 EventHandler<TEventArgs>
是用于事件处理的委托。在.NET
中,事件通常基于这些委托来定义。EventHandler
没有参数,而 EventHandler<TEventArgs>
接受一个 TEventArgs
类型的参数,其中 TEventArgs
通常是从 EventArgs
类派生的自定义事件参数类。
Converter
委托:
Converter<TInput, TOutput>
委托用于定义将一种类型转换为另一种类型的方法。它接受一个类型为 TInput
的参数,并返回一个类型为 TOutput
的结果。
这些预定义的委托类型大大简化了代码,特别是在需要传递方法作为参数或回调时。你可以直接使用这些委托,而不必每次都定义自己的委托类型。当然,如果需要更复杂的签名或特定的行为,你仍然可以定义自己的委托类型。
接下来就是事件
事件
事件就是一种特殊的委托
事件的作用就是可以在委托实例化的之后不需要立刻为其注册一个方法:
类似于函数指针赋值为NULL
;
类似于窗口程序中我放了一个button
,但是这个button
什么都不做。
那么事件怎么声明呢,事件要用委托的类型来声明
class TODO {public static void Main(String[] args) {event Mydelegate Click;}delegate void Mydelegate();
}
可以理解成我实例化了Mydelegate
这个委托,实例化的名字是Click
,而且没有绑定东西
当然我们可以使用预定义的委托
class TODO {private event Action<int> Click;public static void Main(string[] args) {TODO doit = new TODO();doit.Click += (x) => { Console.WriteLine(x); };doit.Click(2);//非法调用不可取}
}
比较好的写法:
using System;class TODO {// 定义一个带有一个int类型参数的Action委托类型的事件 public event Action<int> Click;// 受保护的方法用于触发Click事件 protected virtual void OnClick(int value) {// 检查是否有订阅者 Click?.Invoke(value);}// 公共方法,允许外部调用者触发事件 public void TriggerClick(int value) {OnClick(value);}
}class Program {static void Main(string[] args) {// 创建TODO类的实例 TODO doit = new TODO();// 订阅第一个委托到Click事件 doit.Click += (x) => { Console.WriteLine("第一个委托被调用: " + x); };// 订阅第二个委托到Click事件 doit.Click += (x) => { Console.WriteLine("第二个委托被调用: " + x); };// 通过TODO类的实例方法来触发事件 doit.TriggerClick(1); // 这会依次调用所有订阅了Click事件的处理程序 }
}
其中
protected virtual void OnClick(int value) {// 检查是否有订阅者 Click?.Invoke(value);}
在C#
中,?.
是空条件运算符(null-conditional operator
)的语法。这个运算符用于在访问对象的成员(属性、方法或索引器)之前检查该对象是否为null
。如果对象为null
,则整个表达式会立即返回null
,而不会引发NullReferenceException
异常。
空条件运算符提供了一种简洁的语法来避免在访问对象成员之前进行显式的null
检查。这可以使代码更简洁,并减少由于忘记进行null
检查而导致的运行时错误。
在 C#
中,事件是特殊的成员,它们只能由定义它们的类内部触发,而不能从类的外部直接调用。
举个例子
class demo {private int a;public int A {get { return a; }set { a = value;aChanged?.Invoke();}}public event Action aChanged;}class TODO {public static void Main() {demo dd = new demo();dd.aChanged += () => Console.WriteLine("a was changed.");dd.A = 1;dd.A += 2;}
}
输出:
a was changed.
a was changed.
微软为我们提供了标准事件模式
提供了一个委托类型:EventHandler
返回值void
两个参数object
,EventArgs
object
是触发事件的对象本身,EventArgs
是触发事件包含的信息
统一特征
(object sender, EventArgs e)
命名小寄巧
名字 +
动词过去式
On +
名词 +
动词过去式
Raise +
名词 +
动词
动词
EventRised
PropertyChanged
CollectionChangedOnPropertyChangedRaisePropertyChangeClick
KeyDown