zoukankan      html  css  js  c++  java
  • 稍加改进的Switch/Case扩展方法

    引言

    sc 鹤冲天的《c#扩展方法奇思妙用》系列给了我很多启示,其中的很多用法大大提升了代码编写效率,最近小研究了一下他提供的《c#扩展方法奇思妙用变态篇三:switch/case组扩展》一文提供的扩展方法,并依照自己的习惯重新实现了一下,现分享一下我的实现。

    范例

    先看一下他原文中的范例:

    image

    我觉得这里唯一不爽的就是高亮的那部分,因为这里必需要显式声明类型并作后续处理,我希望直接让编译器推导出返回类型,来看看改进后的实现:

    string typeName = typeId.Switch()

     .CaseReturn(0, "食品")

     .CaseReturn(1, "饮料")

     .CaseReturn(2, "酒水")

     .CaseReturn(3, "毒药")

     .DefaultReturn("未知")

     .ReturnValue;

    在一连串的CaseReturn/DefaultReturn后通过ReturnValue属性就可以访问到最终的返回结果,这样就可以直接使用,不需再传入表达式进行后续处理了。

    这样做还有一个好处,就是当代码段位于方法体中时,可以直接return结果,而如果像原文那样传入表达式来处理结果的话,是不能直接return的,在表达式里return仅会被视为表达式级别的return。

    还有就是这样的代码段可以放在方法的参数中使用,这会很方便,可以将其视作三元表达式的加强版。

    在原文中还有这样一种重载:

    image

    看高亮部分,这个位置的参数只是用来对判断依据进行调整,我觉得完全没有必要,写成password.Length.Switch(……)就行了呀,所以我没有依照此重载方式实现。

    以我的实现方式书写的等效代码为:

    private static Color GetBackColor2(string password)

    {

        var r = password.Length.Switch()

            .CaseReturn(f => f <= 4, 255)

            .CaseReturn(f => f <= 6, 192)

            .CaseReturn(7, 128)

            .CaseReturn(8, 64)

            .DefaultReturn(0)

            .ReturnValue;

        return Color.FromArgb(255, 255 - r, 255 - r);

    }

    虽然不传入操作结果处理表达式就能安享编译器的自动推导功能,但是有时操作结果表达式还是十分有用的,比如原文中的这个范例:

    image

    这里首先让所有筛选过程都禁用了break,然后通过传入的表达式将依次返回的结果相累加。

    对于这种应用来说,就必须传入自定义的表达式来对结果进行处理了,也就必须要显式声明类型了,我对此的实现也与之相仿,但是我要求传入的表达式具有两个参数,第一个参数是新获得的返回结果,第二个参数是目前的返回结果,并要求该表达式返回经过处理后的结果,以代入下一次处理或用作最终结果,等效代码为:

    private static int GetReward(int count)

    {

        return count.Switch((int n, int o) => n + o)

            .CaseReturn(f => f > 5, 1, false)

            .CaseReturn(f => f > 10, 10, false)

            .CaseReturn(f => f > 20, 100, false)

            .CaseReturn(f => f > 50, 1000, false)

            .CaseReturn(f => f > 100, 1000)

            .ReturnValue;

    }

    有人可能奇怪,为什么方法名都是CaseReturn、DefaultReturn这样的带个Return呢?这不是很啰嗦嘛?直接以Case命名不好吗?

    这是因为我还实现了另一种形式:无返回值形式

    参看下面的代码:

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)

    {

        if (IsBusy)

            MessageBox.Show(@"下载正在进行中,是否要关闭此窗口并中止下载?

    - 关闭窗口并中止下载

    - 仅关闭窗口,不中止下载

    取消 - 不进行任何操作", "提示"

         , MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question).Switch()

         .CaseRun(DialogResult.Yes, f => this.Stop())

         .CaseRun(DialogResult.No, f => { })

         .DefaultRun(f => e.Cancel = true);

    }

    这段代码通过CaseRun、DefaultRun方法执行传入的表达式,其功能就是根据不同对话框选项进行不同的处理,很容易理解。

    之所以将CaseRun与CaseReturn区别命名,而不是重载,是因为CaseReturn也有类似于CaseRun的重载,即第二个参数也是表达式的重载。

    虽然CaseReturn的重载中要求传入的表达式要有返回值(Func<T,T>),而CaseRun的第二个参数不要求返回值(Action<T>),但是在传入单句的Lambda表达式的时候容易产生歧义。

    比如表达式中执行了一个带有返回值的方法,由于单句Lambda表达式不需显式使用return关键字,所以编译器就不能确切推导出要执行的是哪一个重载,这样编译器就可能会抓狂,然后其视为最吻合的Func<T,T>形式重载,而编写者可能仅仅是想执行一下该方法,并不希望获取返回值并反映到结果中去。

    (情景范例:在单句Lambda表达式中调用了对数据库执行SQL语句的方法,该方法会返回受影响的记录总数,而程序员可能是希望仅仅执行一下SQL语句就好了,但恰巧此Switch()方法链的返回结果被推导为int类型,程序就将此表达式匹配到Func<T,T>形式重载,就这样糊里糊涂地让这个返回值影响到了最终的返回结果)

    所以如果不区别命名的话,在第二个参数中通过单句Lambda表达式执行带有返回值的方法时,程序就会倾向按照Func<T,T>的形式来执行,如是这样的话,出现问题的可能性不大,但一旦赶巧出现歧义判定问题,就很烦人,而且很难查出来,故此我要保留这种区别命名的形式。

    要点提示

    在使用中,以上展示的各种方法及其重载都可以混搭使用,但需注意以下几点:

    • Default类方法仅允许在语句链的末端使用,但其后可以追加同类的多个方法。
      确切的说应该是:Default类方法的后面不允许再使用Case类方法。
    • 在Switch()之后,只有当首次书写CaseReturn方法时,返回结果的类型才被定性,此后的所有CaseReturn方法都将要遵从此类型。
      因为在Switch()方法里没有显式声明任何类型,所以这个返回类型推导工作被延后到首次书写CaseReturn方法时完成。
      Switch的有参数重载形式不具备此延迟推导特性。
    • 如果一直没有书写CaseReturn方法,那么在书写DefaultRun方法后,方法链将结束,无法书写后续的方法链,也无法获取返回结果。
      这个设计是很自然的,因为其后书写什么都没有什么实际意义了,这里提示出来就是怕不知道的人误以为是BUG。

    不只是替代switch

    由于这个Switch扩展方案支持传入表达式做判定和执行,所以它还完全可以用于替代if……else if……else语句,比如下面代码中,底部的那段代码与注释掉的那段代码就是等价的:

    var str = "test";

    var on=true;

    var day = DateTime.Today;

    //if (str.StartsWith("t") && on) str = "1";

    //else if (str.Length > 9) str = "2";

    //else if (str == "none") { str = "3"; on = false; }

    //else if (day < DateTime.Now) str = "4";

    //else on = false;

    str.Switch()

        .CaseRun(f => f.StartsWith("t") && on, f => str = "1")

        .CaseRun(f => f.Length > 9, f => str = "2")

        .CaseRun("none", f => { str = "3"; on = false; })

        .CaseRun(f => day < DateTime.Now, f => str = "4")

        .DefaultRun(f => on = false);

     

    扩展的意义

    这样的扩展除了让代码显得更复杂以衬托出作者之牛B深奥之外,还有什么优点?

    优点就是能在单句Lambda表达式中使用,这样就能让你更深奥一层……

    哈哈,玩笑,不只是单句Lambda表达式,在充当方法的参数时,三元表达式又不够用的情况下,这样的扩展就大有用武之地了,你可以不必大费周章地再去定义临时的变量并给它赋值,或者专门建立一个方法来解决这类简单的判别问题。

    它的形式可能不算优雅,但它能够让你的代码结构变得优雅一些,并让你专注于解决手头的问题,而不是在代码页中上翻下抄瞎忙活。

     

    总结

    再次感谢鹤冲天给我们带来了那么多的启示,让我们一同将扩展方法发挥到淋漓尽致吧,这是我们自制的语法糖啊:)

    下载

    扩展方法源代码:http://www.uushare.com/user/icesee/file/2155948

    本文的XPS版本:http://www.uushare.com/user/icesee/file/2155951

       

  • 相关阅读:
    单据体内2个字段比较
    立账模式
    余额
    单据服务校验设置
    值更新事件(触发带基础属性到指定字段)
    重建索引 ,压缩数据库
    数据库自动备份
    BZOJ 4597: [Shoi2016]随机序列 线段树 + 思维
    BZOJ 4399: 魔法少女LJJ 线段树合并 + 对数
    BZOJ 2217: [Poi2011]Lollipop 构造 + 思维
  • 原文地址:https://www.cnblogs.com/SkyD/p/1589682.html
Copyright © 2011-2022 走看看