在.NET平台下,委托类型用来定义和响应应用程序中的回调。
.NET委托类型是一个类型安全的对象,指向可以以后调用的其它方法。【.NET委托支持多路广播和异步方法调用】
#.NET委托类型
##非泛型委托
1.
委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。
委托类型包含3个重要的信息:
所调用的方法的名称
方法的参数【可选】
方法的返回值类型【可选】
.NET委托可以指向静态方法,页可以指向实例方法。
一个委托对象被创建并提供上述信息后,可以在运行时动态调用其指向的方法。
.NET Framework中每个委托都被自动赋予同步或异步访问方法的能力。可以直接调用另一个辅助执行线程上的方法。
C#编译器处理委托类型时,先自动产生一个派生自System.MulticastDelegate的密封类。
2.1.Invoke()
以同步方式调用委托对象维护的每个方法
同步Invoke()不能在C#中直接调用
2.2.BeginInvoke()和EndInvoke()
能在第二个执行线程上异步调用当前方法。
// 定义一个委托
// 指向传入两个整数,返回一个整数的方法
public delegate int BinaryOp(int x, int y);
// 这是编译器默认的合成版本
public sealed class BinaryOp : System.MulticastDelegate
{
public int Invoke(int x, int y);
public IAsyncResult BeginInvoke(int x, int y, 【委托参数列表】
AsyncCallback cb, object state);
public int【委托返回值类型】 EndInvoke(IAsyncResult result);
}
public delegate string MyOtherDelegate(out bool a, ref bool b, int c);
// 编译器默认的合成版本
public sealed class MyOtherDelegate : System.MulticastDelegate
{
public string【返回值类型】 Invoke(out bool a, ref bool b, int c【参数列表】);
public IAsyncResult BeginInvoke(out bool a, ref bool b, int c, 【参数列表】
AsyncCallback cb, object state);
public string【返回值类型】 EndInvoke(out bool a, ref bool b,【包含out/ref修饰的参数这里也要放上】 IAsyncResult result);
}
// 通用合成类表示
public sealed class DelegateName : System.MulticastDelegate
{
public delegateReturnValue Invoke(allDelegateInputRefAndOutParams);
public IAsyncResult BeginInvoke(allDelegateInputRefAndOutParams, AysncCallback cb, object state);
public delegateReturnValue EndInvoke(allDelegateRefAndOutParams, IAsyncResult result);
}
需要学习的基础设施:
System.MulticastDelegate / System.Delegate
namespace SimpleDelegate
{
public delegate int BinaryOp(int x, int y);
public class SimpleMath
{
public static int Add(int x, int y)
{
return x + y;
}
public static int Subtract(int x, int y)
{
return x - y;
}
}
class Program
{
static void Main()
{
Console.WriteLine("***Simple Delegate Example***\n");
BinaryOp b = new BinaryOp(SimpleMath.Add);
// b(10, 10)等价与 b.Invoke(10, 10)
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Console.ReadLine();
}
static void DisplayDelegateInfo(Delegate delObj)
{
foreach(Delegate d in delObj.GetInvocationList())
{
Console.WriteLine("Method Name:{0}", d.Method);
Console.WriteLine("Type Name:{0}", d.Target);
}
}
}
}
3.使用委托发送对象状态通知–使用委托定义Car
定义将通知发送给调用者的委托类型
声明Car类中每个委托类型的成员变量
在Car上创建辅助函数使调用者能指定由委托成员变量保存的方法
修改Accelerate()方法以在适当情形下调用委托的调用列表
public class Car
{
public int CurrentSpeed{get; set;}
public int MaxSpeed{get; set;}
public string PetName{get; set;}
private bool carIsDead;
public Car()
{
MaxSpeed = 100;
}
public Car(string name, int maxSp, int currSp)
{
CurrentSpeed = currSp;
MaxSpeed = maxSp;
PetName = name;
}
// 1.定义委托类型
public delegate void CarEngineHandler(string msgForCaller);
// 2.定义每个委托类型的成员变量
private CarEngineHandler listOfHandlers;
// 3.向调用者添加注册函数
public void RegisterWithCarEngine(CarEngineHandler methodToCall)
{
listOfHandlers = methodToCall;
}
public void Accelerate(int delta)
{
if(carIsDead)
{
if(listOfHandlers != null)
{
listOfHandlers("Sorry, this car is dead...");
}
}
else
{
CurrentSpeed += delta;
if(10 == (MaxSpeed - CurrentSpeed)
&& listOfHandlers != null)
{
listOfHandlers("Careful buddy! Gonna blow!");
}
if(CurrentSpeed >= MaxSpeed)
{
CarIsDead = true;
}
else
{
Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
}
}
}
}
class Program
{
static void Main()
{
Console.WriteLine("***Delegates as event enablers***\n");
Car c1 = new Car("SlugBug", 100, 10);
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
Console.WriteLine("***Speeding up***");
for(int i = 0; i < 6; i++)
{
c1.Accelerate(20);
}
Console.ReadLine();
}
public static void OnCarEngineEvent(string msg)
{
Console.WriteLine("\n***Message From Car Object***");
Console.WriteLine("=>{0}", msg);
Console.WriteLine("*************\n");
}
}
4.支持多路广播
一个委托对象可以维护一个可调用方法的列表。
给一个委托对象添加多个方法时,重载+=操作符即可。
public class Car
{
public void RegisterWithCarEngine(CarEngineHandler methodToCall)
{
// 等价的表达
// if(listOfHandlers == null)
// listOfHandlers = methodToCall;
// else
// Delegate.Combine(listOfHandlers, methodToCall);
listOfHandlers += methodToCall;
}
...
}
5.从委托的调用列表移除成员
public class Car
{
public void UnRegisterWithCarEngine(CarEngineHandler methodToCall)
{
listOfHandlers -= methodToCall;
}
}
6.方法组转换语法
static void Main()
{
Console.WriteLine("***Delegates as event enablers***\n");
Car c1 = new Car("SlugBug", 100, 10);
// 常常使用委托对象只是为了传递作为构造函数参数的方法名称
// 为了简化操作,C#提供了一种叫方法组转换的方法。
// 允许我们在调用以委托作为参数的方法时直接提供方法名称【创建委托对象并将方法名称作为参数传递给对象构造函数隐式执行】
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
Car.CarEngineHandler handler2 = new Car.CarEngineHandler(OnCarEngineEvent2);
c1.RegisterWithCarEngine(handler2);
...
}
//
static void Main()
{
...
c1.RegisterWithCarEngine(CallMeHere);
...
}
static void CallMeHere(string msg)
{
Console.WriteLine("=>Message from Car:{0}", msg);
}
##泛型委托
1.
namespace GenericDelegate
{
// 泛型委托
public delegate void MyGenericDelegate<T>(T arg);
class Program
{
static void Main()
{
Console.WriteLine("***Generic Delegates***\n");
MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget);
strTarget("Some string data");
MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);
intTarget(9);
Console.ReadLine();
}
static void StringTarget(string arg)
{
Console.WriteLine("arg in uppercase is:{0}", arg.ToUpper());
}
static void IntTarget(int arg)
{
Console.WriteLine("++arg is:{0}", ++arg);
}
}
}
2.泛型Action<>和Func<>委托
使用委托在应用中回调时,须:
自定义一个与要指向的方法的格式相匹配的委托
创建自定义委托的实例,将方法名作为构造函数的参数
通过调用委托对象的Invoke()来间接调用该方法
许多情况下,只需要接受一组参数并返回一个值的委托,委托名无关紧要。
这时可使用Action<>和Func<>。
// Action<>委托的一个目标
static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)
{
ConsoleColor previous = Console.ForegroundColor;
Console.ForegroundColor = txtColor;
for(int i = 0; i < printCount; i++)
{
Console.WriteLine(msg);
}
Console.ForegroundColor = previous;
}
static void Main(string[] args)
{
Console.WriteLine("***Fun with Action and Func***");
// Action<>委托只能指向返回void的方法。
// 如果要指向具有返回值的方法【又不不想编写自定义委托】,可以使用Func<>
Action<string, ConsoleColor, int> actionTarget = new Action<string, ConsoleColor, int>(DisplayMessage);
actionTarget("Action Message!", ConsoleColor.Yellow, 5);
Console.ReadLine();
}
///
static int Add(int x, int y)
{
return x + y;
}
static string SumToString(int x, int y)
{
return (x+y).ToString();
}
static void Main()
{
// 最后一个参数固定为调用方法的返回值类型
Fun<int, int, int> funcTarget = new Func<int, int, int>(Add);
int result = funcTarget.Invoke(40, 40);
Console.WriteLine("40+40={0}", result);
Func<int, int, string> funcTarget2 = new Func<int, int, string>(SumToString);
string sum = funcTarget2(90, 300);
Console.WriteLine(sum);
}
#C#事件
为了简化自定义方法的构建来为委托调用列表增加和删除方法,C#提供了event关键字。
编译器处理event关键字时,自动提供注册和注销方法以及必要的委托类型成员变量。这些委托成员变量总是声明为私有的。
event节省了完整的委托定义。
1.定义一个事件
定义一个委托类型,它包含在事件触发时将要调用的方法。
通过C# event关键字用相关委托声明这个事件。
public class Car
{
// 这个委托用来与Car的事件协作
public delegate void CarEngineHandler(string msg);
// 这种汽车可以发送这些事件
public event CarEngineHandler Exploded;
public event CarEngineHandler AboutToBlow;
...
}
public void Accelerate(int delta)
{
if(carIsDead)
{
if(Exploded != null)
{
Exploded("Sorry, this car is dead...");
}
}
else
{
CurrentSpeed += delta;
if(10 == MaxSpeed - CurrentSpeed
&& AboutToBlow != null)
{
AboutToBlow("Careful buddy! Gonna blow!");
}
if(CurrentSpeed >= MaxSpeed)
{
carIsDead = true;
}
else
{
Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
}
}
}
向调用者发送一个事件,就如通过名称和相关联委托定义的必需参数来指定事件这么简单。
C#事件会扩展为两个隐藏的公共方法。一个带add_前缀,另一个带remove_前缀。
3.监听传入的事件
// NameOfObject.NameOfEvent += new RelatedDelegate(functionToCall);
Car.CarEngineHandler d = new Car.CarEngineHandler(CarExploded);
myCar.Exploded += d;
// NameOfObject.NameOfEvent -= new RelatedDelegate(functionToCall);
myCar.Exploded -= d;
class Program
{
static void Main()
{
Console.WriteLine("***Fun With Events***\n");
Car c1 = new Car("SlugBug", 100, 10);
c1.AboutToBlow += new Car.CarEngineHandler(CarIsAlmostDoomed);
c1.AboutToBlow += new Car.CarEngineHandler(CarAboutToBlow);
Car.CarEngineHandler d = new Car.CarEngineHandler(CarExploded);
c1.Exploded += d;
Console.WriteLine("***Speeding up***");
for(int i = 0; i < 6; i++)
{
c1.Accelerate(20);
}
c1.Exploded -= d;
Console.WriteLine("\n***Speeding up***");
for(int i = 0; i < 6; i++)
{
c1.Accelerate(20);
}
Console.ReadLine();
}
}
public static void CarAboutToBlow(string msg)
{
Console.WriteLine(msg);
}
public static void CarIsAlmostDoomed(string msg)
{
Console.WriteLine("=>Critical Message from Car:{0}", msg);
}
public static void CarExploded(string msg)
{
Console.WriteLine(msg);
}
// 进一步简化事件注册,可以使用方法组转换【在需要传递委托对象的时候,直接传递方法名】
static void Main()
{
Car c1 = new Car("SlugBug", 100, 10);
c1.AboutToBlow += CarIsAlmostDoomed;
c1.AboutToBlow += CarAboutToBlow;
c1.Exploded += CarExploded;
....
}
4.创建自定义的事件参数
public class EventArgs
{
public static readonly EventArgs Empty;
public EventArgs();
}
public class CarEventArgs : EventArgs
{
public readonly string msg;
public CarEventArgs(string message)
{
msg = message;
}
}
public class Car
{
public delegate void CarEngineHandler(object sender, CarEventArgs e);
public void Accelerate(int delta)
{
if(carIsDead)
{
if(Exploded != null)
{
Exploded(this, new CarEventArgs("Sorry, this car is dead..."));
}
}
...
}
public static void CarAboutToBllow(object sender, CarEventArgs e)
{
Console.WriteLine("{0} says:{1}", sender, e.msg);
}
...
}
5.泛型EventHandler委托
// T就是自定义的EventArgs类型
// EventHandler<T>
public class Car
{
// 不再需要一个自定义的委托类型
// 仅仅适用于 接受object作为参数1,接受EventArgs派生类型作为参数2,返回类型为void的委托
public event EventHandler<CarEventArgs> Exploded;
public event EventHandler<CarEventArgs> AboutToBlow;
...
}
static void Main()
{
Console.WriteLine("***Prim and Proper Events***\n");
Car c1 = new Car("SlugBug", 100, 10);
c1.AboutToBlow += CarIsAlmostDoomed;
c1.AboutToBlow += CarAboutToBlow;
EventHandler<CarEventArgs> d = new EventHandler<CarEventArgs>(CarExploded);
c1.Exploded += d;
...
}
#C#匿名方法
1.
可以在事件注册时,直接将一个委托与一段代码关联。
这种代码称为匿名方法。
用委托对象的地方,可以用匿名方法,也可以用Lambda表达式。
class Program
{
static void Main()
{
Console.WriteLine("***Anonymous Methods***\n");
Car c1 = new Car("SlugBug", 100, 10);
// 注册事件处理程序作为匿名方法
c1.AboutToBlow += delegate
{
Console.WriteLine("Eek! Going too fast!");
};
c1.AboutToBlow += delegate(object sender, CarEventArgs e)
{
Console.WriteLine("Message from Car:{0}", e.msg);
};
c1.Exploded += delegate(object sender, CarEventArgs e)
{
Console.WriteLine("Fatal Message from Car:{0}", e.msg);
};
for(int i = 0; i < 6; i++)
{
c1.Accelerate(20);
}
Console.ReadLine();
}
}
匿名方法内可以访问匿名方法所在作用域内的本地变量。
这些变量称为匿名方法的外部变量。
匿名方法不能访问定义方法中的ref或out参数
匿名方法中的本地变量不能与外部方法中的本地变量重名
匿名方法可以访问外部类作用域的实例变量或静态变量。
匿名方法内的本地变量可以与外部类的成员变量同名。
#Lambda表达式
1.
Lambda表达式只是用更简单的方式来写匿名方法。
public List<T> FindAll(Predicate<T> match);
public delegate bool Predicate<T>(T obj);
class Program
{
static void Main()
{
Console.WriteLine("***Fun With Lambdas***\n");
TraditionalDelegateSyntax();
Console.ReadLine();
}
static void TraditionalDelegateSyntax()
{
List<int> list = new List<int>();
list.AddRange(new int[]{20, 1, 4, 8, 9, 44});
// 匿名方法版本
//List<int> evenNumbers = list.FindAll(
//delegate(int i)
//{
// return (i%2)==0;
//});
// Lambda表达式版本
// 编译器依据Lambda表达式上下文和底层委托,推断i为int
// List<int> evenNumbers = list.FindAll(i =>(i%2)==0);
// 或List<int> evenNumbers = list.FindAll((int i) =>(i%2)==0);
Predicate<int> callback = new Predicate<int>(IsEvenNumber);
List<int> evenNumbers = list.FindAll(callback);
...
}
static bool IsEvenNumber(int i)
{
return (i % 2) == 0;
}
}
Lambda表达式可以应用于任何匿名方法或强类型委托的场合。
2.Lambda表达式形式
ArgumentsToProcess => StatementsToProcessThem
Lambda表达式的参数可以显式和隐式类型化。
参数隐式类型化时,参数类型推断得出。返回值类型由表达式结果推断。
static void LambdaExpressionSyntax()
{
List<int> list = new List<int>();
list.AddRange(new int[]{20, 1, 4, 8, 9, 44});
List<int> evenNumbers = list.FindAll(
(i)=>
{
Console.WriteLine("value of i is currently:{0}", i);
bool isEven = ((i%2)==0);
return isEven;
});
}
3.含多个或零个参数的Lambda表达式
public class SimpleMath
{
public delegate void MathMessage(string msg, int result);
private MathMessage mmDelegate;
public SetMathHandler(MathMessage target)
{
mmDelegate = target;
}
}
class Program
{
static void Main()
{
SimpleMath m = new SimpleMath();
m.SetMathHandler(
// 无参版本()
// 显式版本(string msg, int result)
(msg, result)=>
{
Console.WriteLine("Message:{0}, Result:{1}", msg, result);
});
m.Add(10, 10);
}
}