委托是函数指针的“升级版”,委托是一种类,类是数据类型,所以委托也是一种数据类型,委托与类都属于引用类型。
指向多个方法的委托叫做多播委托。
函数的两种调用方式:直接调用/间接调用 显而易见的,通过委托调用函数属于间接调用。
//委托的声明
public delegate void MyDelegate();// ←(这里声明的委托参数列表为空)//注意! 该委托声明在名称空间体内 而不是在类体里
// ↑ ↑
//delegate关键字 返回值类型(这里声明的委托返回值类型为空)
//该委托就可以指向Log方法,因为Log方法的返回值类型为空,且参数列表为空! 委托是需要 类型兼容的! 类型兼容! 类型兼容!
public class MyClass
{
//类的声明
public void Log()
{
//方法的声明
Console.WriteLine("简单例子");
}
}
注意声明委托的位置!委托的声明一般如同类的声明,是在名称空间体内的,如果你将委托声明在了类体里,那委托就会声明成一个嵌套类型。
委托的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
我们可以根据实际情况自定义委托的返回值类型与参数列表,注意委托所指向的方法必须要与委托类型兼容(返回值类型与参数列表)。
static void Main(string[] args)
{
MyClass myClass = new MyClass();
MyDelegate myDelegate = new MyDelegate(myClass.Log);//myDelegate委托指向myClass中的Log方法
}
注意!为什么我们在new MyDelegate(myClass.Log)的Log后没有加()呢?因为在这里我们并不是去调用myClass.Log这个方法,所以不需要加上该方法的构造器,在这里我们只是需要myClass.Log这个方法的方法名!
Action委托 无返回值类型(一定无),16种重载,最多支持16个参数。
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Action action = new Action(calculator.Report);
calculator.Report();//直接调用
action.Invoke();//间接调用
action();
上图中的Action委托 无参数列表,指向Calculator类中的Report方法(同样无返回值,无参数列表,类型兼容!)如果你想使用带有参数的Action委托,在Action后+<>(如Action<>)。
class Calculator
{
public void Report()
{
Console.WriteLine("I Have 3 methods");
}
public int Add(int a, int b)
{
int result=a+b;
return result;
}
public int Sub(int a, int b)
{
int result=a-b;
return result;
}
}
Func委托,有返回值类型(一定有),17种重载,最多支持16个参数(可以无参数)。
Func func1 = new Func(calculator.Add);
Func func2 = new Func(calculator.Sub);
int x = 100;
int y = 200;
int z = 0;
z = func1.Invoke(x, y);//间接调用
Console.WriteLine(z);
z = func2.Invoke(x, y);//间接调用
Console.WriteLine(z);
Console.ReadLine();
上图中的func1和func2委托,均含有两个int类型的参数,并且返回值类型也为int。
他们所指向的Add与Sub方法与fun1和func2类型兼容!
模板方法就如下面代码中的WrapProduct(Func
internal class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func func1 = new Func(productFactory.MakePizza);//func1委托指向productFactory.MakePizza方法
Func func2 = new Func(productFactory.MakeToyCar);//func2委托指向productFactory.MakeToyCar方法
//func1 委托与func2委托的返回值类型均为Product 指向的方法返回值类型也均为Product 类型兼容!
Box box1 = wrapFactory.WrapProduct(func1);//此时相当于将productFactory.MakePizza方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法 所以称为模板方法
Box box2 = wrapFactory.WrapProduct(func2);//此时相当于将productFactory.MakeToyCar方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法 所以称为模板方法
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
Console.ReadLine();
}
}
class Product
{
public string Name{ get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
public Box WrapProduct (Func getProduct)//参数为一个委托类型参数
{
Box box=new Box();
Product product = getProduct.Invoke();
box.Product= product;
return box;
}
}
class ProductFactory//ProductFactory类中的两个Make方法的返回值类型都为Product
{
public Product MakePizza()
{
Product product= new Product();
product.Name = "Pizza";
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Car";
return product;
}
}
模板方法与回调方法的最大区别就在于,模板方法,“我”一定使用,而回调方法,“我可以不用,但不能没有”,回调方法是可以被选择是否使用的(根据具体情况),并且模板方法(用Func)有返回值,回调方法(用Action)无返回值,下面代码中的例子就是产品的价格大于等于50,才选择执行回调方法的逻辑。
internal class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Logger logger = new Logger();
Action log = new Action(logger.Log);
//Action委托log 与logger.Log方法类型兼容
Func func1 = new Func(productFactory.MakePizza);
Func func2 = new Func(productFactory.MakeToyCar);
Box box1 = wrapFactory.WrapProduct(func1,log);//此时相当于又将log方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法
Box box2 = wrapFactory.WrapProduct(func2,log);//此时相当于又将log方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
Console.ReadLine();
}
}
class Logger
{
public void Log(Product product)
{
Console.WriteLine("Product'{0}'created at{1}.Price is{2}",product.Name,DateTime.UtcNow,product.Price);
}
}
class Product
{
public string Name{ get; set; }
public int Price { get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
public Box WrapProduct (Func getProduct,Action logCallback)//将Action委托指向的方法当作参数传进了WrapProduct方法
{
Box box=new Box();
Product product = getProduct.Invoke();
if (product.Price >= 50)//回调方法与模板方法的最大区别,“可以不用,但不能没有”,即我不一定执行你的方法
{
logCallback(product);
}
box.Product= product;
return box;
}
}
class ProductFactory//ProductFactory类中的两个Make方法的返回值类型都为Product
{
public Product MakePizza()
{
Product product= new Product();
product.Name = "Pizza";
product.Price = 10;
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Car";
product.Price= 50;
return product;
}
}
单播委托指的是一个委托只指向了一个方法,如下面的代码所示,action1,action2,action3三个委托都分别指向了stu1,stu2,stu3的一个DoHomework方法,它们均为单播委托
static void Main(string[] args)
{
Student stu1=new Student() { ID=1,PenColor=ConsoleColor.Red};
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1();//action1.Inovke() 间接调用
action2();
action3();
}
我们来看一下Student类,和上面的代码的执行效果。为我们对多播委托的理解作一个铺垫。
public class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for(int i = 0; i < 3; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s)",this.ID,i);
Thread.Sleep(1000);
}
}
}
下图为多播委托,我们可以看到action1委托指向了3个方法(封装了3个方法)。
一个委托封装多个方法可以通过+=操作符完成,注意如果此时漏掉了+号,只写一个=号的话,会导致一个委托的重置(这也为我们后面引出事件作了铺垫,因为委托的使用是不安全的,就像字段的使用是不安全的,我们会更倾向于使用属性)!
该多播委托的执行结果与之前的单播委托的执行结果完全相同,我们可以得出,多播委托所封装方法的执行顺序,与封装他们时的封装顺序一致。
static void Main(string[] args)
{
Student stu1=new Student() { ID=1,PenColor=ConsoleColor.Red};
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
Action action1 = new Action(stu1.DoHomework);
action1 += stu2.DoHomework;
action1+= stu3.DoHomework;
action1();
Console.ReadLine();
}
这里我们要注意中英文语言的差异性,区分C#中的同步与异步与我们平时生活用语的不同。
同步调用有:1.直接同步调用(这里的直接 指的是直接通过方法名调用方法)2.间接同步调用 3.多播委托(这也是间接调用的一种)
下图中红色的线代表主线程,蓝色, 绿色,黄色代表stu1,stu2,stu3的Dohomework方法,我们程序的执行是线性的,从左到右依次执行,所以说是同步的(按顺序 依次 执行)
下面是直接同步调用
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
stu1.DoHomework();//通过方法名直接调用
stu2.DoHomework();
stu3.DoHomework();
for(int i=0;i<5;i++)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Main thread do something{0}", i);
Thread.Sleep(1000);
}
Console.ReadLine();
}
下面是间接同步调用
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1();//action1.Inovke() 间接调用
action2();
action3();
for (int i=0;i<5;i++)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Main thread do something{0}", i);
Thread.Sleep(1000);
}
Console.ReadLine();
}
多播委托(间接调用)
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
Action action1 = new Action(stu1.DoHomework);
action1 += stu2.DoHomework;
action1 += stu3.DoHomework;
action1(); //多播委托
for (int i=0;i<5;i++)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Main thread do something{0}", i);
Thread.Sleep(1000);
}
Console.ReadLine();
}
以上三种同步调用的运行结果都是一致的,程序按顺序依次执行,执行结果如下图所示
下面我们来看看异步调用,我们可以这样理解异步,当我们在干一件事的同时也在干另一件事(好比小时候我们一边写作业一边看电视,这两件事是我们在同时做的)。
我们通过BeginInvoke方法(参数列表两个参数一般填null值)进行隐式的异步调用, 下面的代码就是三个委托与主线程同时执行,我们来看看执行结果就明白了。
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1.BeginInvoke(null,null);//隐式的异步调用
action2.BeginInvoke(null,null);
action3.BeginInvoke(null,null);
for (int i=0;i<5;i++)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Main thread do something{0}", i);
Thread.Sleep(1000);
}
Console.ReadLine();
}
下面的执行结果我们可以看出,这三个委托与主线程确实是同时执行的,但是为什么打印的颜色出现了问题呢? 这涉及到了我们操作系统的一些知识,由于我们的三个分支线程与主线程都在同时执行,且都去访问了打印颜色这个资源,导致了资源的争抢且发生了冲突,为了避免产生资源争抢而产生冲突,我们通常会为线程加锁(操作系统知识,这里不在过多讨论)
下面是显式的异步调用,它与隐式的异步调用一样,同样会发生资源的争抢。
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));//显式异步调用
Thread thread2 = new Thread(new ThreadStart(stu1.DoHomework));
Thread thread3 = new Thread(new ThreadStart(stu1.DoHomework));
for (int i=0;i<5;i++)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Main thread do something{0}", i);
Thread.Sleep(1000);
}
Console.ReadLine();
}
这是我对B站Up主TimothyLiu(刘铁猛老师!!!)与BeaverJoe老师对于委托的学习总结,如有错漏,恳请指出,谢谢!