您的位置:首页 > 编程语言 > C#

C#知识点整理(2)-高级语言学习模型的建立之深入剖析委托

2017-10-17 11:33 609 查看
1.引言:

学习学习再学习,这不是鸡汤,展开了讲是学习了学习的方法论再去学习。这是李笑来先生提倡的一种终身学习思维。

面对一个新的知识体系,我们不应仅仅只依赖于感性思维和过去经历所累积下来的经验,而要理性的思考,这门学科是如何建立的、前人是如何探索学习这门学科的等等。理解了这种思维,才能一直保持学习效率上的高效。

本篇文章介绍了一种高级语言学习模型,换句话说是我在学习C#的过程中搭建起来的一个知识框架。文章以C#中的委托为例,通过深入的剖析委托来解释这个模型。当然受限于我自己的知识存量,模型会有些不完善,欢迎各位的拍砖指正。

2.高级语言学习模型图解:



基础语法:如C#语言中的基本数据类型,如何定义变量,对象与类型的概念,数组,类型强制转换,继承,封装,多态等基本概念。

高级特性及应用:如C#中的泛型,集合,委托,lambda表达式,事件,LINQ,正则表达式等等。

机器模型:在C#中若是专注于winform开发,则应该对windows API 或是操作系统有一个基本的认识,知道如何进行不同系统间的程序交互。

若是专注于web开发,如:ASP.NET中基本的请求响应模型,ViewState原理。

计算机网络的五层模型:物理层,链路层,网络层,传输层,应用层等。

在这一阶段我们逐渐从语言在单一场景化的应用中脱离了出来,不再是一个只知道敲代码的程序猿了,而逐渐开始了解各种基本原理,知道了语言的高级特性的实现机制,知道了诸如像ASP.NET这些框架的实现原理。

设计模式及原则:这一部分我的探究很少,现在主要在看《head first 设计模式》这本书,对于程序设计模式和原则,需要深厚的实践经验的积累,才能从中逐渐提炼出来抽象的原则,或是理解为何一门语言的复杂特性需要这样来实现。

以SRP原则举例:

SRP—即单一职责原则,该原则规定每个类都应该有一个责任,这个责任应该由类完全封装。

它的所有服务应向责任侠义看齐。简单总结:一个类或模块应该有一个且只有一个理由去改变。

原来我在看书《C#高级编程(第九版)》的时候其实一直很奇怪,在介绍.C#的一些新特性时,书中总是首先抛出一个基本类型,比如集合中的 List,这只是一个包含有集合这个概念的基本功能的类,如图所示:



要想对这个类进行扩充或者是需要具有某种特定行为特征的集合类时,

如:队列(Queue),栈(Stack),链表(LinkedList),字典(Dictionary)和集(SortedSet,HashSet),

总是以接口的方式来定义该行为特征,实现了指定单一责任的接口之后,也就完善了这个类应有的功能。如图示:



一些接口的说明:



在模型中的上下两个模块没有进行说明,是由于这需要一个实例来配合说明。在下面的深入剖析委托中,我会逐步的介绍这两个模块。

3.深入剖析委托:

委托属于C#这门语言的一个高级特性,并且可以说是最核心的高级特性之一。关于下述内容的撰写,有四个参考链接:


1 2 3 4

3.1 委托的定义:

委托是对函数的引用。

委托是一种类型安全的函数回调机制。

从C语言开始就有使用函数指针来创建回调函数的用法(代码截图如下),到了C#,委托可以被视为一个更高级的指针,它不仅仅能把地址指向另一个函数,而且还能传递参数,返回值等多个信息。

.Net还为委托对象自动生成了同步、异步的调用方式,我们只要使用 BeginInvoke、EndInvoke 方法就可以抛开 Thread 而直接使用多线程调用 。

对于委托的说法有许多,但从本质上说委托仍旧是一个类,在C#中用delegate关键字声明一个委托:

public delegate void Log();


再次使用Reflector将这段代码反编译之后,就可以看到它的层次结构:



从图中可以很清晰的看出Log—>MulticastDelegate—>Delegate这种继承机制。

尽管委托继承自System.MulticastDelegate类,但我们并不能显示地声明一个继承自System.MulticastDelegate类的委托。委托必须使用delegate关键字声明,编译器会自动为我们生成继承代码。

由于委托的本质是类,所以可它就可以被定义在任何地方,既可以定义在类的内部,也可以定义在类的外部。

那么既然委托也是一个类,那委托也是由四个部分组成:字段,属性,方法,事件。

其中最重要的是这样的三个字段:





3.2 委托的使用实例:

如果看到这里,对于为什么要使用委托,以及委托的作用是什么还不是很清晰的话,可以参考这篇文章

[把委托说透]http://www.cnblogs.com/kirinboy/archive/2009/08/26/intensive-delegate-1.html)

现在我提供一个使用委托来传递函数方法的实例:

通常我们使用冒泡排序时指的是对数字进行排序,即对定义过,“大于号、小于号”这类关系运算符的数据类型进行排序,排序代码如下:

//冒泡排序写法1
public static void Sort(int[] sortArray)
{
bool swapped = true;
do
{
swapped = false;
for (int i = 0; i < sortArray.Length - 1; i++)
{
if (sortArray[i] > sortArray[i + 1])
{
int temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}

//顺便贴出来之前看过的一个比较**的冒泡排序写法 2:
public static void BubbleSorterInInt(int[] sortArray)
{
int count = sortArray.Length - 1, lastChange, i = 0;
while (i < count)
{
lastChange = 0;
for (i = 0; i < count; i++)
{
if (sortArray[i] > sortArray[i + 1])
{
//不使用中间变量进行数据元素交换
sortArray[i] = sortArray[i] + sortArray[i + 1];//a=a+b
sortArray[i + 1] = sortArray[i] - sortArray[i + 1];//b=a-b
sortArray[i] = sortArray[i] - sortArray[i + 1];//a=a-b
lastChange = i;
}
}
i = 0;
count = lastChange;//判断是否进行了数组元素交换
}
//输出int数组
for (int j = 0; j < sortArray.Length; j++)
{
Console.Write(sortArray[j]);
Console.Write(",");
}
}


那么现在问题来了,由于这种排序算法只能对int类型的数据管用,如果我想对其它数据类型的对象排序呢?难不成每次有了新的数据类型,都要新写一个排序函数吗?

这样就太过麻烦了,并且每次都要修改已经封装好了的类,这样就不太好。所以我们考虑将冒泡排序写法1中的函数功能再次进行分离。

如上述代码所示,函数功能可以表示成两个部分:

1.判断数组中前后两个元素的大小,并决定谁先谁后;

2.根据大小比较结果,改变数组中元素的顺序。

据此我们可以将功能1交给使用者来实现,而改进后的冒泡排序只实现功能2。改进代码如下:

//实现功能2的冒泡排序写法3
public static void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
{
bool swapped = true;
do
{
swapped = false;
for (int i = 0; i < sortArray.Count-1; i++)
{
if (comparison(sortArray[i+1],sortArray[i]))
{
T temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}


从上述代码段中可以看出,功能1的实现是使用者通过传入自定义的comparison函数来实现的。

其中:
Func<T,T,bool>
是.NET Framework定义好的一个委托,“T,T”是传入的参数类型,bool是返回值类型。

为了应用冒泡排序写法3,我们需要先自定义一个类:

public class Employee
{
public Employee(string name,decimal salary)
{
this.Name = name;
this.Salary = salary;
}

public string Name { get; private set; }

public decimal Salary { get; private set; }

public override string ToString()
{
return string.Format("{0},{1:C}", Name, Salary);
}
//实现比较该类型的两个对象的大小的功能
public static bool CompareSalary(Employee e1, Employee e2)
{
return e1.Salary < e2.Salary;
}
}


然后在控制台中进行输出:


static void Main(string[] args)
{
Employee[] employees =
{
new Employee("Bugs Bunny",20000),
new Employee("Elmer Fudd",10000),
new Employee("Daffy Duck",25000),
new Employee("Wile Coyote",1000000.38m),
new Employee("Foghorn Leghorn",23000),
new Employee("RoadRunner",50000)
};
BubbleSorter.Sort<Employee>(employees, Employee.CompareSalary);

foreach (var employee in employees)
{
Console.WriteLine(employee.ToString());
}
Console.ReadKey();
}


以上就已经实现了对一般类型对象进行排序的功能。

在上述代码中我们应用到了委托来传入用户自定义的比较函数,通过比较函数返回的布尔值来判断比较中的对象谁先谁后。

3.3
Action<T>和Func<T>委托


泛型
Action<T>
委托表示引用了一个void返回类型的方法,该委托类存在许多变体,可以传递至多16中不同的参数类型。

其中没有泛型参数的Action类调用没有参数的方法。

泛型
Func<T>
可以以类似的方法使用。Func允许调用带返回参数的方法。与Action类似,Func也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。
Func<out TResult>
委托类型可以调用带返回类型的且无参数的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c# 经验 委托