该文章由本人原创发布,最新版本现已迁移至:编写高质量代码的50条黄金守则-Day 04(首选字符串插值) | .Net中文网

编写高质量代码的50条黄金守则-Day 04(首选字符串插值),本文由比特飞原创发布,转载务必在文章开头附带链接:编写高质量代码的50条黄金守则-Day 04(首选字符串插值) | .Net中文网

该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则。

1、概述

从 C# 6.0 开始,微软开始为 .net 引入字符串插值,通过为字符串加 $ 前缀的方式,提供了强大的语法糖,为字符串的处理带来更好的使用体验。相比于传统的字符串处理 string.Format 来说,其使用方式更加的灵活。今天,我们来为大家解密字符串插值的庐山真面目。

2、通过反编译查看IL,探究字符串插值的本质

接下来,我们先来准备环境:

namespace EffectiveCoding04 {public class Program {private class User : IFormattable {public string Foo { get; set; }public string ToString(string format, IFormatProvider formatProvider) {return $"My name is {Foo}";}}private static string GetValue() {return "foo";}private static IEnumerable<User> GetValues() {yield return new User() { Foo = "foo 1" };yield return new User() { Foo = "foo 2" };yield return new User() { Foo = "foo 3" };yield return new User() { Foo = "foo 4" };yield return new User() { Foo = "foo 5" };}//准备数据/* var condition = true;var value = (User)null; */}}

User 类实现 IFormattable 接口,提供字符串格式化功能,GetValue 方法返回一个字符串,GetValues 方法返回一个字符串序列 。

3、使用方法

1、基本使用方法

我们先来看看字符串插值的基本用法:

Console.WriteLine($"Value1 is {Math.PI}");Console.WriteLine($"Value2 is {Math.PI.ToString()}");

以下是输出结果:

Value1 is 3.141592653589793Value2 is 3.141592653589793

占位说明:。接下来我们看看它们的 IL:

/* (35,13)-(35,55) C:\Users\Administrator\source\repos\EffectiveCoding04\EffectiveCoding04\Program.cs *//* 0x00000281 7209000070 */ IL_0005: ldstr "Value1 is {0}"/* 0x00000286 23182D4454FB210940 */ IL_000A: ldc.r8 3.141592653589793/* 0x0000028F 8C1B000001 */ IL_0013: box [System.Runtime]System.Double/* 0x00000294 281600000A */ IL_0018: call string [System.Runtime]System.String::Format(string, object)/* 0x00000299 281700000A */ IL_001D: call void [System.Console]System.Console::WriteLine(string)/* 0x0000029E 00 */ IL_0022: nop/* (36,13)-(36,66) C:\Users\Administrator\source\repos\EffectiveCoding04\EffectiveCoding04\Program.cs *//* 0x0000029F 7225000070 */ IL_0023: ldstr "Value2 is "/* 0x000002A4 23182D4454FB210940 */ IL_0028: ldc.r8 3.141592653589793/* 0x000002AD 0C */ IL_0031: stloc.2/* 0x000002AE 1202 */ IL_0032: ldloca.s V_2/* 0x000002B0 281800000A */ IL_0034: call instance string [System.Runtime]System.Double::ToString()/* 0x000002B5 281900000A */ IL_0039: call string [System.Runtime]System.String::Concat(string, string)/* 0x000002BA 281700000A */ IL_003E: call void [System.Console]System.Console::WriteLine(string)/* 0x000002BF 00 */ IL_0043: nop

IL 的代码有些疑惑,我们利用 dnSpy 反编译看看结果:

Console.WriteLine(string.Format("Value1 is {0}", 3.141592653589793));Console.WriteLine("Value2 is " + 3.141592653589793.ToString());

我们可以看到 $”Value1 is {Math.PI}”; 被 string.Format(“Value1 is {0}”, 3.141592653589793) 所替换, $”Value2 is {Math.PI.ToString()}” 被 “Value2 is ” + 3.141592653589793.ToString() 所替换。

2、配合格式化参数使用

字符串插值可以配合格式化参数一起使用:

Console.WriteLine($"Value3 is {Math.PI.ToString("F2")}");Console.WriteLine($"Value4 is {Math.PI:F2}");

以下是 IL 的结果:

/* 0x000002C0 723B000070 */ IL_0044: ldstr "Value3 is "/* 0x000002C5 23182D4454FB210940 */ IL_0049: ldc.r8 3.141592653589793/* 0x000002CE 0C */ IL_0052: stloc.2/* 0x000002CF 1202 */ IL_0053: ldloca.s V_2/* 0x000002D1 7251000070 */ IL_0055: ldstr "F2"/* 0x000002D6 281A00000A */ IL_005A: call instance string [System.Runtime]System.Double::ToString(string)/* 0x000002DB 281900000A */ IL_005F: call string [System.Runtime]System.String::Concat(string, string)/* 0x000002E0 281700000A */ IL_0064: call void [System.Console]System.Console::WriteLine(string)/* 0x000002E5 00 */ IL_0069: nop/* (39,13)-(39,58) C:\Users\Administrator\source\repos\EffectiveCoding04\EffectiveCoding04\Program.cs *//* 0x000002E6 7257000070 */ IL_006A: ldstr "Value4 is {0:F2}"/* 0x000002EB 23182D4454FB210940 */ IL_006F: ldc.r8 3.141592653589793/* 0x000002F4 8C1B000001 */ IL_0078: box [System.Runtime]System.Double/* 0x000002F9 281600000A */ IL_007D: call string [System.Runtime]System.String::Format(string, object)/* 0x000002FE 281700000A */ IL_0082: call void [System.Console]System.Console::WriteLine(string)/* 0x00000303 00 */ IL_0087: nop

以下是 dnSpy 反编译的结果:

Console.WriteLine("Value3 is " + 3.141592653589793.ToString("F2"));Console.WriteLine(string.Format("Value4 is {0:F2}", 3.141592653589793));

我们可以看到 $”Value3 is {Math.PI.ToString(“F2″)}” 被 “Value3 is ” + 3.141592653589793.ToString(“F2″) 所替换,$”Value4 is {Math.PI:F2}” 被 string.Format(“Value4 is {0:F2}”, 3.141592653589793) 所替换。

C# 会对字符串插值中的 : 做特殊处理,认为后面的部分为格式化参数。那如果我们就是想要输出 : 的话,应该如何处理呢?

3、错误的示例

你可能会使用以下方式输出 ::

Console.WriteLine($"Value5 is {condition ? Math.PI : Math.PI.ToString("F2")}"); //无法编译通过Console.WriteLine($@"Value6 is {(condition ? Math.PI : Math.PI.ToString("F2"))}"); //无法编译通过

然后以上代码却无法编译通过,因为编译器认为 : 后面的为格式化参数,导致语法解析错误,那应该如何处理呢?答应是使用 @ 操作符,显式指定不转义。

4、配合条件运算符

字符串插值配合条件运算符一起使用:

Console.WriteLine($@"Value7 is {(condition ? Math.PI.ToString() : Math.PI.ToString("F2"))}");

以下是 IL 的结果:

/* 0x00000304 7279000070 */ IL_0088: ldstr "Value7 is "/* 0x00000309 06 */ IL_008D: ldloc.0/* 0x0000030A 2D18 */ IL_008E: brtrue.s IL_00A8/* 0x0000030C 23182D4454FB210940 */ IL_0090: ldc.r8 3.141592653589793/* 0x00000315 0C */ IL_0099: stloc.2/* 0x00000316 1202 */ IL_009A: ldloca.s V_2/* 0x00000318 7251000070 */ IL_009C: ldstr "F2"/* 0x0000031D 281A00000A */ IL_00A1: call instance string [System.Runtime]System.Double::ToString(string)/* 0x00000322 2B11 */ IL_00A6: br.s IL_00B9/* 0x00000324 23182D4454FB210940 */ IL_00A8: ldc.r8 3.141592653589793/* 0x0000032D 0C */ IL_00B1: stloc.2/* 0x0000032E 1202 */ IL_00B2: ldloca.s V_2/* 0x00000330 281800000A */ IL_00B4: call instance string [System.Runtime]System.Double::ToString()/* 0x00000335 281900000A */ IL_00B9: call string [System.Runtime]System.String::Concat(string, string)/* 0x0000033A 281700000A */ IL_00BE: call void [System.Console]System.Console::WriteLine(string)/* 0x0000033F 00 */ IL_00C3: nop

以下是 dnSpy 反编译的结果:

Console.WriteLine("Value7 is " + (condition ? 3.141592653589793.ToString() : 3.141592653589793.ToString("F2")));

我们可以看到 $@”Value7 is {(condition ? Math.PI.ToString() : Math.PI.ToString(“F2″))}” 被转换成了 “Value7 is ” + (condition ? 3.141592653589793.ToString() : 3.141592653589793.ToString(“F2”)) ,这样编译顺利进行。

5、配合空值传播运算符使用

字符串插值可以配合空值传播运算符一起使用:

Console.WriteLine($"Value8 is {value?.Foo ?? "value is null"}");

以下是 IL 的结果:

/* 0x00000340 728F000070 */ IL_00C4: ldstr "Value8 is "/* 0x00000345 07 */ IL_00C9: ldloc.1/* 0x00000346 2D03 */ IL_00CA: brtrue.s IL_00CF/* 0x00000348 14 */ IL_00CC: ldnull/* 0x00000349 2B06 */ IL_00CD: br.s IL_00D5/* 0x0000034B 07 */ IL_00CF: ldloc.1/* 0x0000034C 2805000006 */ IL_00D0: call instance string EffectiveCoding04.Program/User::get_Foo()/* 0x00000351 25 */ IL_00D5: dup/* 0x00000352 2D06 */ IL_00D6: brtrue.s IL_00DE/* 0x00000354 26 */ IL_00D8: pop/* 0x00000355 72A5000070 */ IL_00D9: ldstr "value is null"/* 0x0000035A 281900000A */ IL_00DE: call string [System.Runtime]System.String::Concat(string, string)/* 0x0000035F 281700000A */ IL_00E3: call void [System.Console]System.Console::WriteLine(string)/* 0x00000364 00 */ IL_00E8: nop

以下是 dnSpy 反编译的结果:

Console.WriteLine("Value8 is " + (((value != null) ? value.Foo : null) ?? "value is null"));

可以看到 $”Value8 is {value?.Foo ?? “value is null”}” 被编译器替换成 “Value8 is ” + (((value != null) ? value.Foo : null) ?? “value is null”) 。

6、配合方法使用

字符串插值也可以配合方法一起使用:

Console.WriteLine($"Value9 is {GetValue()}");

以下是 IL 的结果:

/* 0x00000365 72C1000070 */ IL_00E9: ldstr "Value9 is "/* 0x0000036A 2801000006 */ IL_00EE: call string EffectiveCoding04.Program::GetValue()/* 0x0000036F 281900000A */ IL_00F3: call string [System.Runtime]System.String::Concat(string, string)/* 0x00000374 281700000A */ IL_00F8: call void [System.Console]System.Console::WriteLine(string)/* 0x00000379 00 */ IL_00FD: nop

以下是 dnSpy 反编译的结果:

Console.WriteLine("Value9 is " + Program.GetValue());

直接被编译器转换成传统的字符串连接操作。

7、配合 Linq 使用

字符串插值配合 Linq 一起使用的示例:

Console.WriteLine($"Value10 is {GetValues().FirstOrDefault(r => r.Foo == "foo 3")}");

以下是 IL 的结果:

/* 0x0000037A 72D7000070 */ IL_00FE: ldstr "Value10 is {0}"/* 0x0000037F 2802000006 */ IL_0103: call class [System.Runtime]System.Collections.Generic.IEnumerable<code data-enlighter-language="generic" class="EnlighterJSRAW">1<class EffectiveCoding04.Program/User> EffectiveCoding04.Program::GetValues()/* 0x00000384 7E06000004 */ IL_0108: ldsfld class [System.Runtime]System.Func</code>2<class EffectiveCoding04.Program/User, bool> EffectiveCoding04.Program/'<>c'::'<>9__3_0'/* 0x00000389 25 */ IL_010D: dup/* 0x0000038A 2D17 */ IL_010E: brtrue.s IL_0127/* 0x0000038C 26 */ IL_0110: pop/* 0x0000038D 7E05000004 */ IL_0111: ldsfld class EffectiveCoding04.Program/'<>c' EffectiveCoding04.Program/'<>c'::'<>9'/* 0x00000392 FE0613000006 */ IL_0116: ldftn instance bool EffectiveCoding04.Program/'<>c'::'<Main>b__3_0'(class EffectiveCoding04.Program/User)/* 0x00000398 731B00000A */ IL_011C: newobj instance void class [System.Runtime]System.Func<code data-enlighter-language="generic" class="EnlighterJSRAW">2<class EffectiveCoding04.Program/User, bool>::.ctor(object, native int)/* 0x0000039D 25 */ IL_0121: dup/* 0x0000039E 8006000004 */ IL_0122: stsfld class [System.Runtime]System.Func</code>2<class EffectiveCoding04.Program/User, bool> EffectiveCoding04.Program/'<>c'::'<>9__3_0'/* 0x000003A3 280100002B */ IL_0127: call !!0 [System.Linq]System.Linq.Enumerable::FirstOrDefault<class EffectiveCoding04.Program/User>(class [System.Runtime]System.Collections.Generic.IEnumerable<code data-enlighter-language="generic" class="EnlighterJSRAW">1<!!0>, class [System.Runtime]System.Func</code>2<!!0, bool>)/* 0x000003A8 281600000A */ IL_012C: call string [System.Runtime]System.String::Format(string, object)/* 0x000003AD 281700000A */ IL_0131: call void [System.Console]System.Console::WriteLine(string)/* 0x000003B2 00 */ IL_0136: nop

以下是 dnSpy 反编译的结果:

Console.WriteLine(string.Format("Value10 is {0}", Program.GetValues().FirstOrDefault((Program.User r) => r.Foo == "foo 3")));

有意思的是, Linq 也被编译器转换成了 string.Format 的调用方式。

4、总结

1、字符串插值会被编译器转换,而传统的 string.Format 仅仅是方法的调用 :

2、字符串插值更加灵活,也更加强大,推荐使用字符串插值的方式操作字符串。

开发人员应牢记以上开发守则,否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你、唾弃你。

该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则。

该文章由本人原创发布,最新版本现已迁移至:编写高质量代码的50条黄金守则-Day 04(首选字符串插值) | .Net中文网

编写高质量代码的50条黄金守则-Day 04(首选字符串插值)相关推荐

  1. 编写高质量代码的50条黄金守则-Day 03(首选is或as而不是强制类型转换)

    该文章由本人原创发布,最新版本现已迁移至:编写高质量代码的50条黄金守则-Day 03(首选is或as而不是强制类型转换) | .Net中文网. 编写高质量代码的50条黄金守则-Day 03(首选is ...

  2. 编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)

    该文章由本人原创发布,最新版本现已迁移至:编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const) | .Net中文网. 编写高质量代码的50条黄金守则-Day 02(首选 ...

  3. 编写高质量代码的50条黄金守则-Day 01(首选隐式类型转换)

    该文章由本人原创发布,最新版本现已迁移至:编写高质量代码的50条黄金守则-Day 01(首选隐式类型转换) | .Net中文网. 编写高质量代码的50条黄金守则-Day 01(首选隐式类型转换),本文 ...

  4. 编写高质量代码的50条黄金守则

    该文章由本人原创发布,最新版本现已迁移至:编写高质量代码的50条黄金守则 | .Net中文网. 概述 该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则. ...

  5. 编写高质量代码改善C#程序的157个建议——建议50:在Dispose模式中应区别对待托管资源和非托管资源...

    建议50:在Dispose模式中应区别对待托管资源和非托管资源 真正资源释放代码的那个虚方法是带一个bool参数的,带这个参数,是因为我们在资源释放时要区别对待托管资源和非托管资源. 提供给调用者调用 ...

  6. 读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)

    读书笔记:编写高质量代码--web前端开发修炼之道 这本书看得断断续续,不连贯,笔记也是有些马虎了,想了解这本书内容的童鞋可以借鉴我的这篇笔记,希望对大家有帮助. 笔记有点长,所以分为一,二两个部分: ...

  7. 【备注】【C42】《编写高质量代码:改善Python程序的91个建议》PDF

    内容简介: 在通往"Python技术殿堂"的路上,本书将为你编写健壮.优雅.高质量的Python代码提供切实帮助!内容全部由Python编码的实践组成,从基本原则.惯用法.语法.库 ...

  8. 荐书与免费送书:《编写高质量代码改善 Python 程序的 91 个建议》

    为了学习如何打理好微信公众号,Python猫我关注了好几个python技术公众号.然后发现这些同行们都在免费送资源,或者抽奖送书耶.于是,我也去参与抽奖,竟然侥幸抽中啦一本<Python数据科学 ...

  9. 《编写高质量代码:改善c程序代码的125个建议》——建议2-6:防止无符号整数回绕...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议2-6,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

最新文章

  1. iPhone开发进阶(9)--- 用SQLite管理数据库
  2. 加深Java基础,做了20道题选择题!简答题没做
  3. 一个简单的DELPHI自定义事件的例子(转)
  4. 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 使用反射获取方法创建本应用的 dexElements | 各版本创建 dex 数组源码对比 )
  5. Dell R240 1U机架式服务器 资料
  6. DM368开发 --IPNC 设置过程
  7. wpf在异步中给前台赋值
  8. [蓝桥杯2016初赛]方格填数-next_permutation
  9. mysql binlog DDL_mysql一个事务中有DDL语句的binlog情况
  10. ubantu下面U盘无法识别
  11. Composite_组合模式_PHP语言描述
  12. Android Studio 创建aar包与引用
  13. java项目报告书_Java项目报告模版.doc
  14. 利用TreeWalk提高网速及其在vista中的安装方法
  15. 短信验证php_php如何实现短信验证
  16. 【独行秀才】macOS Big Sur 11.6正式版(20G165)原版镜像
  17. SVN强制编写注释才能提交,提交中不允许删除文件操作。
  18. 使用LaTeX输入矩阵
  19. C语言—操作符和表达式
  20. 百度网盘终于不限速了!

热门文章

  1. 知识点old1908
  2. python-turtle库-01
  3. metadata.js
  4. 轻雨物联网解决方案:农业物联网的市场前景分析
  5. 记一次转不过弯的递归
  6. 什么是 Unix 以及它为什么这么重要?
  7. 我想做产品,实现一个非常优秀的电脑桌面记事本加闹钟
  8. Dropbox 官方中文版!最优秀实用的免费跨平台文件网络同步网盘云存储服务
  9. 引用类型和原始类型的对比(java)
  10. RabbitMq 3.0.1 技术预演资料