C#委托之我见

委托的使用方式很简单,了解一下基本语法就可以开撸了。但是使用委托的真正难题是不知道应用场景,就像习得了一门新功夫,但是却找不到任何施展拳脚的地方。这个难题一直困然着我,直到最近仿佛有所领悟,所以赶紧记下这可能尚不成熟的观点。如果有什么错误,还望各位看官指正。

解耦合

其实委托最大的作用是解耦合,转移程序方法的功能定义方。在不使用委托的情况下,方法的功能和行为(能做的事)都是由方法提供方决定的,方法一经定义,能做的事情也就固定了,这就相当于方法是静态的。但是如果方法使用了委托参数类型,方法功能的定义方就发生了转移,此时方法能做什么事是由方法的调用方决定的。这样就相当于方法有了生命力,这种生命力是方法的调用方赋予的。并且方法的可重用性得到了提高,以前是做一件事情,现在是做一类事情。同时,委托可以看做是把方法作为方法的参数,这样会避免掉一些不必要的判断(因为作为参数的方法会定义做什么事情,不用再额外判断),简化程序逻辑。

Talk is cheap,show me the code.

方法作为方法的参数,避免掉不必要的判断

我们写程序时经常会遇到这样一种情况。在分支判断中,每个分支中做的操作都可以归属于一类事情,方法的签名也能保持一致。这时可以考虑使用委托消除掉这些分支判断。假设现在要做一个四则运算的功能,其拥有四个方法,它们的签名都相同,都接受两个double输入,并输出一个double。一般的做法是:

public enum Operate
{
    Add,
    Subtrac,
    Multip,
    Divisi
}

public static double Calculate(double a,double b, Operate operate)
{
    switch(operate)
    {
        case Operate.Add:
            return Add(a, b);
        case Operate.Subtrac:
            return Subtrac(a, b);
        case Operate.Multip:
            return Multip(a, b);
        case Operate.Divisi:
            return Divisi(a, b);
        default:
            return 0;
    }
}

public static double Add(double a , double b)
{
    return a + b;
}
public static double Subtrac(double a, double b)
{
    return a - b;
}
public static double Multip(double a, double b)
{
    return a * b;
}
public static double Divisi(double a, double b)
{
    if (b == 0) throw new Exception("分母不能为0");
    return a / b;
}

这样实现有一个缺点,现在是四则运算,万一以后加入其它类型的运算呢?每加入一个类型的运算都要新增一个分支判断,这样的话维护成本就有点高了,也不符合对修改关闭,对扩展开放的开闭原则。要是为每种类型的操作建个类,用多态的思想解决又有点小题大做了。可以考虑使用委托解决这个问题,使用和方法签名相同的委托代替枚举类型的参数。

首先新建一个和方法签名相同的委托类型,然后使用和方法签名相同的委托代替枚举类型的参数:

public delegate double CalculateDelegate(double a, double b);
public static double Calculate(double a, double b, CalculateDelegate operate)
{
    return operate(a, b);
}

调用方决定具体的运算:

static void Main(string[] args)
{
    Calculate(1, 2, Add);
    Calculate(1, 1, Divisi);
}

利用委托来解决这种问题看似很好,但是也有缺点,需要为每一种计算类型定义相应的方法,而且其中有些方法使用频率并不高,程序中可能会大量出现这样的计算方法,维护这些方法反而是不小的负担。C#提供了匿名函数的方式来解决这个问题。

static void Main(string[] args)
{
    Calculate(1, 2, delegate (double a,double b) { return a + b; });
    Calculate(1, 1, delegate (double a, double b) { return a / b; });
}

嗯,解决了上面的问题。但是似乎代码可读性不够高,那就继续进化,C#提供了lambda表达式,让我们以几乎感觉不到委托存在的方式,顺其自然的使用C#委托,原生C#委托几乎被遗忘,委托三步走不复存在,委托=>匿名函数=>lambda表达式 究极进化,C#就是这么强大!

你可以这么玩: Calculate(1,2,(doublea,doubleb)=>{returna+b;});

还可以这么玩: Calculate(1,2,(a,b)=>{returna+b;});

方法调用方决定方法做什么事

C#中的Linq可谓是将委托用到了极致,以Where方法为例,Where方法本身只负责筛选集合中的元素这类事,但是至于具体是哪件事,并不关心。具体做哪件事是由方法的调用方来指定的,比如筛选大于10的元素、或是小于5的元素,这些都是由调用方决定的。方法的灵活性、可重用性都得到了提高。设想一下,如果为每个元素筛选条件规则都去写一个除了筛选条件不同其他操作都相同的新方法,心态爆炸不?使用委托类型的参数,这一切将变得很简单。做一件事情变为做一类事情,至于是哪一件事情,方法调用方来决定喽。

这种方式最重要的应用就是回调函数。

回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

简单理解,当我们将函数A传递给函数B,并由B来执行A时,A就成了一个回调函数(callback functions)。回调函数肯定是方法调用方负责定义的,当方法执行时,满足相应的条件就会触发此回调函数。在C#中实现回调函数的方式就是委托。

假设现在我们有两个方法,一个方法负责将数组中的每个元素翻倍,另一个方法负责加1,现在需要翻倍再加一。如果不使用委托(回调函数),则需要进行两次for循环,性能上无法接受,这个时候就可以使用委托(回调函数)来解决,只需要一次for循环就可以。

不使用委托(回调函数):

public static void Double(int[] nums)
{
    for (int i = 0; i < nums.Length; i++)
    {
        nums[i] = nums[i] * 2;
    }
}

public static void AddOne(int[] nums)
{
    for (int i = 0; i < nums.Length; i++)
    {
        nums[i] = nums[i] + 1;
    }
}
static void Main(string[] args)
{
    int[] nums = { 1, 2, 3 };

    Double(nums);
    AddOne(nums);
}

使用委托(回调函数):

public static void DoubleAndAddOne(int[] nums,Func<int,int> func)
{
    for (int i = 0; i < nums.Length; i++)
    {
        nums[i] = func(nums[i] * 2);
    }
}
DoubleAndAddOne(nums, n => n + 1);

原文发布于微信公众号 - 撸码那些事(lumanxs)

原文发表时间:2018-08-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

sizeof小览

http://blog.csdn.net/scythe666/article/details/47012347

751
来自专栏微信公众号:Java团长

JAVA之旅(一)——基本常识,JAVA概念,开发工具,关键字/标识符,变量/常量,进制/进制转换,运算符,三元运算

比如6:6/2 = 3 余 0 3 / 2 = 1 余 1 那就是从个位数开始011,读起来就是110了

1681
来自专栏小樱的经验随笔

【批处理学习笔记】第二十一课:数值计算

    批处理里面的数值计算功能较弱,只能够进行整型计算,忽略浮点数的小数部分;同时数值计算的范围也受限于系统位数,对于目前较为常见的32位机来说,数值计算能处...

2794
来自专栏JavaQ

烂代码吐槽汇 | 奇葩命名

代码首先是给人看的,其次才是给机器看的。 烂代码特征:可读性差、逻辑混乱、性能低下。 1.奇葩项目(模块)名 项目(模块)名称使用汉语拼音、英汉双拼、超长的字母...

3405
来自专栏Java学习网

Java 8的函数式编程学习

Java 8的函数式编程学习 函数式编程语言是什么? 函数式编程语言的核心是它以处理数据的方式处理代码。这意味着函数应该是第一等级(First-class)的...

2647
来自专栏北京马哥教育

这段代码很Pythonic | 相见恨晚的 itertools 库

1673
来自专栏码农阿宇

C# 6.0中你不知道的新特性

为什么写? 今天去上班的公交上,有朋友在张队(张善友)的微信群里,发了一个介绍C# 6.0新特性的视频,视频7分钟,加上本人英语实在太low,整体看下来是一脸懵...

2554
来自专栏分布式系统和大数据处理

四种简单的排序算法

我觉得如果想成为一名优秀的开发者,不仅要积极学习时下流行的新技术,比如WCF、Asp.Net MVC、AJAX等,熟练应用一些已经比较成熟的技术,比如Asp.N...

1292
来自专栏Albert陈凯

Scala Essentials: 字符串内插值

字符串插值 Scala是一门高度可扩展性的程序设计语言,保持微小的内核,但具有无穷大的扩展能力。例如,「字符串内插」功能,Scala语言并不是原生地支持该特性...

2847
来自专栏五分钟学算法

看完动画你还会不懂 快速排序么

由于LeetCode上的算法题很多涉及到一些基础的数据结构,为了更好的理解后续更新的一些复杂题目的动画,推出一个新系列 -----《图解数据结构》,主要使用动画...

1685

扫码关注云+社区

领取腾讯云代金券