概述:委托
是寻址方法
的.NET版本。委托类不仅包含对方法的引用,也包括对多个方法的引用。
零 委托
0.声明使用委托
- 定义委托使用
delegate
关键字。 - “定义一个委托”实际上是“定义一个新类”。
- 虽然所有的委托都是间接派生自
System.Delegate
,但是C#编译器并不允许定义一个直接或间接派生自System.Delegate
的类。 - C#的委托将方法作为对象封装起来,允许运行时渐渐地绑定一个(一组)方法的调用。
//定义委托
private delegate string GetAString();
static void Main(string[] args)
{
var x = 10;
//用方法给委托变量赋值
//等价于 GetAString delegateObj=new GetAString(x.ToString);
//委托推断
GetAString delegateObj = x.ToString;
//delegateObj() 等价于 delegateObj.Invoke();
Console.WriteLine($"委托调用:{delegateObj()}");
}
1.委托赋值与使用
- 使用老的初始化方法(C#1.0),在编码中应该避免使用这种方法。
- 旧形式的调用
//定义委托
private delegate string GetAString();
//测试代码
//赋值
GetAString delegateObj = new GetAString(x.ToString);
//使用
delegateObj.Invoke()
- 委托推断:C#2.0的新语法,编译器根据方法名来查找方法签名,并验证它同方法的参数类型匹配。
- 新的形式调用
//定义委托
private delegate string GetAString();
//测试代码
//赋值
GetAString delegateObj = x.ToString;
//使用
delegateObj();
2.委托的特点
- 委托是类型安全的,可以确保被调用的方法的签名是正确的。
- 委托只注意方法的签名,而不关心在什么类型的对象上调用该方法,甚至不考虑该方法是静态的或者实例的。
- 所有的委托都是不可变的,就是定义的签名是不可变的。
3.委托的内部机制
- 委托类型的对象模型
- 所有的委托间接派生自
System.Delegate
MethodInfo
属性是System.Reflection.MethodInfo
类型,定义了一个方法的签名。Target
,包含了要调用的方法。如果实例化委托时候传入的是静态方法,那么Target是方法所在对象的类型本身。
4.系统自定义委托
Action<T>
:定义了一个void返回类型的泛型委托。拥有多种变形。Func<T>
:定义了有返回类型的泛型委托。拥有多种变形。
public delegate void Action();
public delegate void Action<in T1>(T1 arg);
/*
*
*/
public delegate void Action<in T1 ,... ,in T16 >(T1 arg1, ... ,T16 arg16)
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T1,out TResult>(T1 arg);
/*
*
*/
public delegate TResult Func<in T1 ,... ,in T16, out TResult>(T1 arg1, ... ,T16 arg16)
private delegate string GetAString();
class TestDelegate
{
public static void ForAction()
=> Console.WriteLine("ForAction Method.");
public static string ForFunc(object obj)
=> obj.ToString();
}
//测试代码
Action a = TestDelegate.ForAction;
var i = "I am string.";
Func<string,string> fun = TestDelegate.ForFunc;
//输出 ForAction Method.
a();
//输出 I am string.
fun(i);
一 匿名函数
0.什么是匿名函数
- C#3.0引入了Lambda表达式。
- Lambda表达式是比匿名方法更加简洁的一种匿名函数语法。
- 在需要使用委托实例的时候,但是该方法又只使用一次,就可以已定义一个匿名函数用于传递给委托实例。
1.匿名方法
- 匿名方法:就是没有实际方法声明的委托实例。
//定义委托
private delegate string GetAString();
/*测试代码*/
//用匿名方法为委托变量赋值
GetAString method = delegate ()
{
return "我是匿名方法。";
};
//输出 "我是匿名方法。"
method();
- 特殊的匿名方法:无法数的匿名方法
//定义委托
private delegate string GetAString();
/*测试代码*/
//用匿名方法为委托变量赋值
GetAString method = delegate
{
return "我是匿名方法。";
};
//输出 "我是匿名方法。"
method();
2.语句Lamdba
- 参数列表用
()
声明;包含一个方法体的{}
(方法体中可以有多条语句);参数列表与方法体用=>
连接。 - 特殊的参数列表
- 可以不用指定参数的类型,如果参数的类型可以推断。
- 如果参数只有一个,可以不用
()
。 - 如果没有参数,那么必须使用
()
。
//标准的 语句Lambda
Func<string, int> GetLength = (string str) =>
{
return str?.Length ?? -1;
};
//省略参数的类型声明
GetLength = (str) =>
{
return str?.Length ?? -1;
};
//当方法没有参数,必须有()
Action PrintString = () =>
{
Console.WriteLine("deletage PrintString");
};
//方法列表只有一个参数,可以省略 ()
Action<string> PrintString01 = param =>
{
Console.WriteLine($"{param}");
};
3.表达式Lambda
- 参数列表用
()
或者生理,方法体只是一个表达式,没有语句块;参数列表与表达式方法体用=>
连接。 - 特殊的参数列表
- 可以不用指定参数的类型,如果参数的类型可以推断。
- 如果参数只有一个,可以不用
()
。 - 如果没有参数,那么必须使用
()
。
- 表达式Lamdba只是语句Lambda的特殊形式,特殊在与方法体只有一个表达式。
//标准的 语句Lambda
Func<string, int> GetLength = (string str) => str?.Length ?? -1;
//省略参数的类型声明
GetLength = (str) => str?.Length ?? -1;
//当方法没有参数,必须有()
Action PrintString = () => Console.WriteLine("deletage PrintString");
//方法列表只有一个参数,可以省略 ()
Action<string> PrintString01 = param => Console.WriteLine($"{param}");
4.匿名函数是无类型的
- 只要匿名函数的方法签名与委托的声明的方法签名,参数与返回类型相互兼容(协变与抗变),就可以隐式转换为委托。
- 匿名函数没有任何固有的类型与之关联,所以不能对一个匿名函数使用
typeof
。通过后面的匿名函数 实现机制,更容易理解。
5.匿名函数的内部机制
-
匿名函数并非CLR内部的固有构造,他们的实现是由C#编译器在编译时候生成的。以内嵌的方式生成方法或者类(匿名函数内引用了外部变量)。
-
CLR生成“方法”
static void Main(string[] args)
{
//标准的 语句Lambda
GetLength a = delegate(string str)
{
return str?.Length ?? -1;
};
a("123");
}
/*类似于*/
private static int __AnonymousMethdo_000000(string str)
{
return str?.Length ?? -1;
}
static void Main(string[] args)
{
//标准的 语句Lambda
GetLength a = __AnonymousMethdo_000000;
a("123");
}
- CLR生成“类”
static void Main(string[] args)
{
var str = "123";
Func<int> getLength = () => str?.Length??-1;
}
/*类似于*/
private sealed class __LocalsDisplayClass_00000000
{
private string Str;
public __LocalsDisplayClass_00000000(string str)
{
Str = str;
}
public int __AnonymousMethdo_000000()
{
return Str?.Length ?? -1;
}
}
static void Main(string[] args)
{
var str = "123";
Func<int> getLength = new __LocalsDisplayClass_00000000(str).__AnonymousMethdo_000000;
}
- 特别注意:不要在匿名函数捕捉循环变量,如果要使用循环变量应该如下使用。
static void Main(string[] args)
{
Action print=delegate { };
var intList = new List<int> {1,2,3,4,5 };
foreach (var item in intList)
{
//print += delegate { Console.WriteLine(item.ToString()); };
//最好使用如下模式,这样传递给委托的每个方法都捕捉的是一个新的变量,而不是循环变量。
//因为捕捉循环变量,可能会造成不一样的结果。(由于C#的版本不同)
var newItem = item;
print += delegate { Console.WriteLine(newItem.ToString()); };
}
print();
}