委托
委托是类类型的一种,委托也是一种类。类类型是引用类型的一种。
委托类型其实就是一种方法的包装器、封装器。
为了使程序可以更换要被调用的函数,同时不想让两个函数紧耦合,此时就需要通过委托来进行间接调用、或者可替换的调用。C和C++中通过函数指针来实现,C#中就要通过委托来实现。Java中如果想要实现这个功能必须要使用接口。
委托是一个函数,或者一组函数的封装器、包装器。
函数的封装叫委托。表面看是函数名的传递,其实是函数逻辑的传递、函数体算法的传递。
委托的声明:
声明委托这种类时看上去很像声明函数签名。需要告诉委托类型,在委托类型包裹函数时,能够包裹什么样的函数、委托的实例里面可以包裹什么样的函数。
如果一个委托的实例可以包装某个方法的话:约定:声明委托类型时用的返回值类型、以及参数列表一定要跟被包裹的函数的返回值类型和参数列表一致才可以。函数签名必须相同。
如上图,声明的委托类型,标明需要委托的函数返回值为空,参数列表也要为空。只有符合这种特征的函数才可以被我们这个MyDele类型的委托来委托。
委托的最基本用法
我们写一个符合委托要求的函数:无返回值,无参数列表。
using System;
namespace DelegateExample
{
delegate void MyDele();
class Program
{
static void Main(string[] args)
{
MyDele dele1 = new MyDele(M1); //实例化委托。
}
static void M1()
{
Console.WriteLine("M1 is called!");
}
}
}
此时dele1这个变量引用着一个MyDele类型的实例,这个实例里“包裹”着M1这个方法。
包裹着这样有啥用呢?
dele1.Invoke();
通过这个变量(委托实例),可以间接调用M1这样一个方法。
这就是委托的最基本用法。
多播委托:MultiCast
委托除了包裹一个函数以外,还可以包裹若干个函数。通过+=操作符来完成。一次调用好几个函数。
delegate void MyDele();
class Program
{
static void Main(string[] args)
{
MyDele dele1 = new MyDele(M1); //实例化委托。
dele1 += M1;
dele1.Invoke();
}
static void M1()
{
Console.WriteLine("M1 is called!");
}
}
那么此时的打印将打印两次。当然也可以加载多个实例的方法。
namespace DelegateExample
{
delegate void MyDele();
class Program
{
static void Main(string[] args)
{
MyDele dele1 = new MyDele(M1); //实例化委托。
dele1 += M1;
Student stu = new Student();
dele1 += stu.SayHello;
dele1.Invoke();
}
static void M1()
{
Console.WriteLine("M1 is called!");
}
}
class Student
{
public void SayHello()
{
Console.WriteLine("I'm a student.");
}
}
}
此处我们还可以这么写
此时除了使用Invoke调用以外,我们还可以直接这样调用:
多举个委托的栗子
using System;
namespace DelegateExample
{
delegate int MyDele(int a, int b);
class Program
{
static void Main(string[] args)
{
MyDele dele1 = new MyDele(M2); //实例化委托。
dele1 += M2;
var result = dele1(1, 2);
Console.WriteLine(result);
}
static int M2(int x, int y)
{
return x + y;
}
}
}
此时委托的特征是返回值为整型,输入参数的列表也是两个整型。
泛型委托
delegate T MyDele<T>(T a, T b);
泛型标识符<>,待特化类型为T。此处有两个函数,我们可以把委托类型特化成int也可以将委托类型特化为double类型。
namespace DelegateExample
{
delegate T MyDele<T>(T a, T b);
class Program
{
static void Main(string[] args)
{
// 特化成int类型。
MyDele<int> deleAdd = new MyDele<int>(Add);
int res = deleAdd(2, 3);
Console.WriteLine(res);
MyDele<double> deleDouble = new MyDele<double>(Mul);
double mulRes = deleDouble(1.2, 1.5);
Console.WriteLine(mulRes);
}
static int Add(int x, int y)
{
return x + y;
}
static double Mul(double x, double y)
{
return x * y;
}
}
}
委托是一个类。特殊的类类型。
封装好的泛型委托:Action。无返回值类型。
一般来说Action用于返回值为空参数列表为空的函数委托,但是Action也有其泛型形式。
Action有16个重载,每个参数个数都不同。
Action<string , int> action = new Action<string, int>(SayRepeat);
这种情况封装的函数方法的参数列表一个是string,一个是int。
封装好的泛型委托:Fuction。有返回值类型。
Func有17个重载,哪怕只泛化了一个类型参数,这一个参数也指明了将要委托的函数的返回值类型。
有时为了编写简化:
var func = new Func<double, int , string>(FunctionName);
func(doubleNum, intNum, stringVar);
Lambda:
1、匿名方法:避免名称空间的污染。
2、inLine方法:一般我们使用函数是先声明,后调用;此处一边声明,一边调用,调用时即生成声明。
Lambda操作符:=>:把参数带入函数体。
正常我们使用函数完成一个逻辑:如下例子。
先声明,后调用。
用lambda如何实现?
static void Main(string[] args)
{
// 直接调用:
int res = Add(1, 2);
Console.WriteLine(res);
// Func委托间接调用
Func<int, int, int> func = new Func<int, int, int>(Add);
func(1, 2);
// Lambda实现方式:
Func<int, int, int> func1 = new Func<int, int, int>((int a, int b) => { return a + b; });
Func<int, int, int> func2 = new Func<int, int, int>((int a, int b) => a + b);
func1(1, 2);
}
static int Add(int a, int b)
{
return a + b;
}
Lambda表达式:(int a, int b) => { return a + b; }。将参数=>发送给函数体。
Lambda表示式的几种写法:
见名知意,言已达意即可。连return都省去了。
static void Main(string[] args)
{
// 直接调用:
int res = Add(1, 2);
Console.WriteLine(res);
// Func委托间接调用
Func<int, int, int> func = new Func<int, int, int>(Add);
func(1, 2);
// Lambda实现方式:
Func<int, int, int> func1 = new Func<int, int, int>((int a, int b) => { return a + b; });
Func<int, int, int> func2 = new Func<int, int, int>((int a, int b) => a + b);
Func<int, int, int> func3 = (int a, int b) => { return a + b; };
func1(1, 2);
func = (x, y) => x * y;
}
static int Add(int a, int b)
{
return a + b;
}
举个栗子:泛型委托配合lambda用到极致的例子。
有这样一个方法:
static void DoSomeCalc<T>(Func<T, T, T>func, T x, T y)
{
T res = func(x, y);
Console.WriteLine(res);
}
先来分析一下这个函数:
-
函数返回值为void空。DoSomeCalc函数名称后面接了一个<T>泛型标识符。<T>标识待特化的类型。
-
整个DoSomeCalc函数需要三个参数:
- 一个参数是Func委托类型的参数func,而且是泛型委托类型,这个Func泛型委托的参数所包裹的函数:需要输入两个待特化类型<T>类型的参数,返回一个<T>类型的返回值。
- 另外两个参数是<T>类型的x和y。
-
整个函数的函数体里面的逻辑是将后两个参数传递给传入进来的委托方法。
-
函数体的逻辑也完全满足了委托包裹函数的实现形式。两个<T>类型通过委托进来的运算输出返回一个<T>类型。
泛型委托的类型推断:使得我们在调用方法的时候可以省去对类型的声明。
上面的函数如何调用?
DoSomeCalc<int>((int a, int b) => { return a * b; }, 100, 200);
其中第一个<int>可以省略。因为泛型委托的类型推断功能。return也可以省略,但是需要一个括号将函数体(lambda表示)包裹起来。
DoSomeCalc(((int a, int b) => a * b), 100, 200);
LINQ:.NET Language Integrated Query
Query查询:可以查询数据库,也可以查询其他东西。Integrated:继承。
通过在LINQ中写算子:Lambda表达式来过滤筛选数据库将泛型委托的类型推断用到了极致。
核心知识:泛型委托具有类型推断功能。