MiCoos 哟,写bug呢?

C#基础 委托

2019-09-25
GHMicoos

概述:委托寻址方法的.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();
}



Similar Posts

上一篇 C#基础 事件

下一篇 C#基础 特性

Comments