.Net扩展方法(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

source:http://www.cnblogs.com/SkyD/archive/2009/10/26/1589682.html

阅读全文
类别:Net 查看评论
posted @ 2010-04-28 14:04  周超亿  阅读(1161)  评论(0编辑  收藏  举报