鹤冲天的《c#扩展方法奇思妙用》系列给了我很多启示,其中的很多用法大大提升了代码编写效率,最近小研究了一下他提供的《c#扩展方法奇思妙用变态篇三:switch/case组扩展》一文提供的扩展方法,并依照自己的习惯重新实现了一下,现分享一下我的实现。
范例
先看一下他原文中的范例:
我觉得这里唯一不爽的就是高亮的那部分,因为这里必需要显式声明类型并作后续处理,我希望直接让编译器推导出返回类型,来看看改进后的实现:
string typeName = typeId.Switch()
.CaseReturn(0, "食品")
.CaseReturn(1, "饮料")
.CaseReturn(2, "酒水")
.CaseReturn(3, "毒药")
.DefaultReturn("未知")
.ReturnValue;
在一连串的CaseReturn/DefaultReturn后通过ReturnValue属性就可以访问到最终的返回结果,这样就可以直接使用,不需再传入表达式进行后续处理了。
这样做还有一个好处,就是当代码段位于方法体中时,可以直接return结果,而如果像原文那样传入表达式来处理结果的话,是不能直接return的,在表达式里return仅会被视为表达式级别的return。
还有就是这样的代码段可以放在方法的参数中使用,这会很方便,可以将其视作三元表达式的加强版。
在原文中还有这样一种重载:
看高亮部分,这个位置的参数只是用来对判断依据进行调整,我觉得完全没有必要,写成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);
}
虽然不传入操作结果处理表达式就能安享编译器的自动推导功能,但是有时操作结果表达式还是十分有用的,比如原文中的这个范例:
这里首先让所有筛选过程都禁用了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
注意:此博客已停止更新,并迁移至blog.SkyDev.cc,后续都将在新地址更新。
转载请遵循此协议:署名 - 非商业用途 - 保持一致
并保留此链接:http://skyd.cnblogs.com/