稍加改进的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 <= 4255)

        .CaseReturn(f => f <= 6192)

        .CaseReturn(7128)

        .CaseReturn(864)

        .DefaultReturn(0)

        .ReturnValue;

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

}

 

 

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

image

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

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

 

private static int GetReward(int count)

{

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

        .CaseReturn(f => f > 51false)

        .CaseReturn(f => f > 1010false)

        .CaseReturn(f => f > 20100false)

        .CaseReturn(f => f > 501000false)

        .CaseReturn(f => f > 1001000)

        .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


本文转自斯克迪亚博客园博客,原文链接:http://www.cnblogs.com/SkyD/archive/2009/10/26/1589682.html,如需转载请自行联系原作者

相关文章
|
JSON JavaScript 搜索推荐
Github 精选 #4 | 让 Github 帮你自动压缩图片!
Github 精选 #4 | 让 Github 帮你自动压缩图片!
Github 精选 #4 | 让 Github 帮你自动压缩图片!
|
开发工具
解决Flutter中ThemeData.primaryColor在AppBar等组件中不生效
解决Flutter中ThemeData.primaryColor在AppBar等组件中不生效
421 1
|
存储 关系型数据库 MySQL
mysql 查看数据库及表大小以及数据库扩容评估
mysql 查看数据库及表大小以及数据库扩容评估
391 4
|
Go API 开发工具
Go etcd 的依赖问题终于解决了。。。
Go etcd 的依赖问题终于解决了。。。
|
JSON API 开发工具
淘宝实时 API 接口丨淘宝商品详情接口(Taobao.item_get)
淘宝商品详情接口(Taobao.item_get)允许开发者获取商品的详细信息,包括基本信息、描述、卖家资料、图片、属性及销售情况等。开发者需注册账号、创建应用并获取API密钥,通过构建请求获取JSON格式数据,注意遵守平台规则,合理使用接口,确保数据准确性和时效性。
1153 9
|
数据可视化 项目管理
如何将SMART目标设定法与项目管理工具的结合使用?
在项目管理中,SMART原则(具体、可衡量、可达成、相关、时限)是设定清晰目标的有效方法。结合现代看板工具,如板栗看板、Trello和Asana,可将目标细化为日常任务,提高团队效率,确保目标顺利实现。
518 1
如何将SMART目标设定法与项目管理工具的结合使用?
|
11月前
|
JSON 监控 安全
深入理解 Python 的 eval() 函数与空全局字典 {}
`eval()` 函数在 Python 中能将字符串解析为代码并执行,但伴随安全风险,尤其在处理不受信任的输入时。传递空全局字典 {} 可限制其访问内置对象,但仍存隐患。建议通过限制函数和变量、使用沙箱环境、避免复杂表达式、验证输入等提高安全性。更推荐使用 `ast.literal_eval()`、自定义解析器或 JSON 解析等替代方案,以确保代码安全性和可靠性。
485 2
|
存储 关系型数据库 MySQL
MySQL存储引擎详述:InnoDB为何胜出?
MySQL 是最流行的开源关系型数据库之一,其存储引擎设计是其高效灵活的关键。InnoDB 作为默认存储引擎,支持事务、行级锁和外键约束,适用于高并发读写和数据完整性要求高的场景;而 MyISAM 不支持事务,适合读密集且对事务要求不高的应用。根据不同需求选择合适的存储引擎至关重要,官方推荐大多数场景使用 InnoDB。
549 7
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的音乐推荐管理系统
基于Java+Springboot+Vue开发的音乐推荐管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的音乐推荐管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
296 8
基于Java+Springboot+Vue开发的音乐推荐管理系统
|
移动开发 编解码 UED
除了 `<audio>` 和 `<video>` 标签,HTML5 还支持哪些多媒体格式?
【10月更文挑战第19天】HTML5对多种多媒体格式的支持,为网页开发者提供了丰富的选择,能够更好地满足不同类型多媒体内容在网页中的展示和交互需求,提升了网页的用户体验和多媒体应用的多样性。