C# 10 新特性 —— 插值字符串优化

C# 10 新特性 —— 插值字符串优化

Intro

字符串应该是我们平时使用的最多的一个类型,从 C# 6 开始我们开始支持了插值字符串,使得我们可以更方便的进行字符串的操作,现在很多分析器也推荐我们使用插值这种写法,这能够使得我们的代码更加清晰和简洁,C# 10 提供了更好的实现方式以及更好的性能

Interpolated string

什么是插值字符串呢?就是 $ 符号开始的类似 $"Hello {name}" 这样的字符串,我们来看下面的示例

var str = $"1233";
var name = "Alice";
var hello = $"Hello {name}!";
var num = 10;
var numDesc = $"The num is {num}";

简单的插值字符串会简化,对于不需要 format 的参数会直接简化为字符串,对于一些简单的字符串拼接,可以简化成 string.Concat,在 C#10/.NET 6 之前的版本中,其他的大多会翻译成 string.Format 的形式,翻译成低版本的 C#  代码则是这样的

string str = "1233";
string name = "Alice";
string hello = string.Concat("Hello ", name, "!");
int num = 10;
string numDesc = string.Format("The num is {0}", num);

对于 string.Format,参数如果是值类型会发生装箱,变为 object,我们从 IL 代码可以看得出来

fa4619887a865200165313e67dd17282.png

IL

插值字符串格式化的时候会使用当前 CultureInfo,如果需要使用不同 CultureInfo 或者手动指定,可以借助 FormattableString/FormattableStringFactory 来实现

8c910750e7d40a0feba4842667216ef0.png

var num = 10;
FormattableString str1 = $"Hello {num}";
Console.WriteLine(str1.Format);
Console.WriteLine(str1.ToString(new CultureInfo("zh-CN")));str1 = FormattableStringFactory.Create("Hello {0}", num);
Console.WriteLine(str1.Format);
Console.WriteLine(str1.ToString(new CultureInfo("en-US")));

对于 C# 10/.NET6 中,则会生成下面的代码:

string str = "1233";
string name = "Alice";
string hello = string.Concat ("Hello ", name, "!");
int num = 10;
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(11, 1);
defaultInterpolatedStringHandler.AppendLiteral("The num is ");
defaultInterpolatedStringHandler.AppendFormatted(num);
string numDesc = defaultInterpolatedStringHandler.ToStringAndClear();

48ccc89976402d202a8ebd8c9cc91d8e.png

IL in C#10/.NET6

在新版本中,会由 DefaultInterpolatedStringHandler 来处理插值字符串,而且这个新的 DefaultInterpolatedStringHandler 是一个结构体并且会有一个泛型方法 AppendFormatted<T> 来避免发生装箱,在 format 的时候性能更优,对于普通的字符串则使用 AppendLiteral() 方法处理,声明如下:

namespace System.Runtime.CompilerServices
{[InterpolatedStringHandler]public ref struct DefaultInterpolatedStringHandler{public DefaultInterpolatedStringHandler(int literalLength, int formattedCount);public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, System.IFormatProvider? provider);public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, System.IFormatProvider? provider, System.Span<char> initialBuffer);public void AppendLiteral(string value);public void AppendFormatted<T>(T value);public void AppendFormatted<T>(T value, string? format);public void AppendFormatted<T>(T value, int alignment);public void AppendFormatted<T>(T value, int alignment, string? format);public void AppendFormatted(ReadOnlySpan<char> value);public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null);public void AppendFormatted(string? value);public void AppendFormatted(string? value, int alignment = 0, string? format = null);public void AppendFormatted(object? value, int alignment = 0, string? format = null);public string ToStringAndClear();}
}

具体实现可以参考:https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs

在 .NET 6 中增加了两个 String 方法来支持使用新的插值处理方式

/// <summary>Creates a new string by using the specified provider to control the formatting of the specified interpolated string.</summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="handler">The interpolated string.</param>
/// <returns>The string that results for formatting the interpolated string using the specified format provider.</returns>
public static string Create(IFormatProvider? provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler) =>handler.ToStringAndClear();/// <summary>Creates a new string by using the specified provider to control the formatting of the specified interpolated string.</summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="initialBuffer">The initial buffer that may be used as temporary space as part of the formatting operation. The contents of this buffer may be overwritten.</param>
/// <param name="handler">The interpolated string.</param>
/// <returns>The string that results for formatting the interpolated string using the specified format provider.</returns>
public static string Create(IFormatProvider? provider, Span<char> initialBuffer, [InterpolatedStringHandlerArgument("provider", "initialBuffer")] ref DefaultInterpolatedStringHandler handler) =>handler.ToStringAndClear();

Custom Interpolated string handler

接着我们来尝试实现一个简单的插值字符串处理器,实现一个最基本的插值字符串处理器需要满足四个条件:

  • 构造函数至少需要两个 int 参数,一个是字符串中常量字符的长度(literalLength),一个是需要格式化的参数的数量(formattedCount)

  • 需要一个 publicAppendLiteral(string s) 方法来处理常量字符的拼接

  • 需要一个 publicAppendFormatted<T>(T t) 方法来处理参数

  • 自定义的处理器需要使用 InterpolatedStringHandler 来标记,处理器可以是 class 也可以是 struct

// InterpolatedStringHandlerAttribute is required for custom InterpolatedStringHandler
[InterpolatedStringHandler]
public struct CustomInterpolatedStringHandler
{// Storage for the built-up stringprivate readonly StringBuilder builder;/// <summary>/// CustomInterpolatedStringHandler constructor/// </summary>/// <param name="literalLength">string literal length</param>/// <param name="formattedCount">formatted count</param>public CustomInterpolatedStringHandler(int literalLength, int formattedCount){builder = new StringBuilder(literalLength);Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");}// Requiredpublic void AppendLiteral(string s){Console.WriteLine($"\tAppendLiteral called: {{{s}}}");builder.Append(s);Console.WriteLine($"\tAppended the literal string");}// Requiredpublic void AppendFormatted<T>(T t){Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");builder.Append(t?.ToString());Console.WriteLine($"\tAppended the formatted object");}public override string ToString(){return builder.ToString();}
}

使用示例如下:

private static void LogInterpolatedString(string str)
{Console.WriteLine(nameof(LogInterpolatedString));Console.WriteLine(str);
}private static void LogInterpolatedString(CustomInterpolatedStringHandler stringHandler)
{Console.WriteLine(nameof(LogInterpolatedString));Console.WriteLine(nameof(CustomInterpolatedStringHandler));Console.WriteLine(stringHandler.ToString());
}// Custom InterpolatedStringHandler
LogInterpolatedString("The num is 10");
LogInterpolatedString($"The num is {num}");

输出结果如下:

LogInterpolatedString
The num is 10literal length: 11, formattedCount: 1AppendLiteral called: {The num is }Appended the literal stringAppendFormatted called: {10} is of type System.Int32Appended the formatted object
LogInterpolatedString
CustomInterpolatedStringHandler
The num is 10

除此之外,我们还可以在自定义的插值字符串处理器的构造器中增加自定义参数,我们可以使用 InterpolatedStringHandlerArgument 来引入更多构造器参数,我们在上面的示例基础上改造一下,改造后 CustomInterpolatedStringHandler代码如下:

[InterpolatedStringHandler]
public struct CustomInterpolatedStringHandler
{private readonly StringBuilder builder;private readonly int _limit;public CustomInterpolatedStringHandler(int literalLength, int formattedCount) : this(literalLength, formattedCount, 0){ }public CustomInterpolatedStringHandler(int literalLength, int formattedCount, int limit){builder = new StringBuilder(literalLength);Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");_limit = limit;}// Requiredpublic void AppendLiteral(string s){Console.WriteLine($"\tAppendLiteral called: {{{s}}}");builder.Append(s);Console.WriteLine($"\tAppended the literal string");}// Requiredpublic void AppendFormatted<T>(T t){Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");if (t is int n && n < _limit){return;}builder.Append(t?.ToString());Console.WriteLine($"\tAppended the formatted object");}public override string ToString(){return builder.ToString();}
}

调用方式我们再增加一种方式以使用新引入的构造器:

private static void LogInterpolatedString(int limit, [InterpolatedStringHandlerArgument("limit")] ref CustomInterpolatedStringHandler stringHandler)
{Console.WriteLine(nameof(LogInterpolatedString));Console.WriteLine($"{nameof(CustomInterpolatedStringHandler)} with limit:{limit}");Console.WriteLine(stringHandler.ToString());
}

做了一个检查,如果参数是 int 并且小于传入的 limit 参数则不会被拼接,来看一下下面的调用

LogInterpolatedString(10, $"The num is {num}");
Console.WriteLine();
LogInterpolatedString(15, $"The num is {num}");

输出结果如下:

literal length: 11, formattedCount: 1AppendLiteral called: {The num is }Appended the literal stringAppendFormatted called: {10} is of type System.Int32Appended the formatted object
LogInterpolatedString
CustomInterpolatedStringHandler with limit:10
The num is 10literal length: 11, formattedCount: 1AppendLiteral called: {The num is }Appended the literal stringAppendFormatted called: {10} is of type System.Int32
LogInterpolatedString
CustomInterpolatedStringHandler with limit:15
The num is

从上面的结果可以看出来,我们的代码是生效的,第一次打印出来了 num,第二次没有打印 num

还有一个特殊的参数,我们可以在构造方法中引入一个 bool 类型的 out 参数,如果这个参数为 false 则不会进行字符串的拼接 Append,我们改造一下刚才的示例,示例代码如下:

public CustomInterpolatedStringHandler(int literalLength, int formattedCount, int limit, out bool shouldAppend)
{shouldAppend = limit < 20;builder = new StringBuilder(shouldAppend ? literalLength : 0);Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");_limit = limit;
}

limit 参数小于 20 时进行字符串的拼接,否则就不输出,测试代码如下

LogInterpolatedString(10, $"The num is {num}");
Console.WriteLine();
LogInterpolatedString(15, $"The num is {num}");
Console.WriteLine();
LogInterpolatedString(20, $"The num is {num}");

输出结果是这样的

literal length: 11, formattedCount: 1AppendLiteral called: {The num is }Appended the literal stringAppendFormatted called: {10} is of type System.Int32Appended the formatted object
LogInterpolatedString
CustomInterpolatedStringHandler with limit:10
The num is 10literal length: 11, formattedCount: 1AppendLiteral called: {The num is }Appended the literal stringAppendFormatted called: {10} is of type System.Int32
LogInterpolatedString
CustomInterpolatedStringHandler with limit:15
The num isliteral length: 11, formattedCount: 1
LogInterpolatedString
CustomInterpolatedStringHandler with limit:20

可以看到,当 limit 是 20 的时候,输出的是空行,没有任何内容

另外我们可以把上面的 Append 方法的返回值改成 bool,如果方法中返回 false 则会造成短路,类似于 ASP.NET Core 中中间件的短路,后面的拼接就会取消,我们再改造一下上面的示例,改造一下 Append 方法

public bool AppendLiteral(string s)
{if (s.Length <= 1)return false;Console.WriteLine($"\tAppendLiteral called: {{{s}}}");builder.Append(s);Console.WriteLine($"\tAppended the literal string");return true;
}// Required
public bool AppendFormatted<T>(T t)
{Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");if (t is int n && n < _limit){return false;}builder.Append(t?.ToString());Console.WriteLine($"\tAppended the formatted object");return true;
}

再来使用 LogInterpolatedString(12, $"The num is {num} and the time is {DateTime.Now}!"); 调用一下试一下,输出结果如下:

literal length: 29, formattedCount: 2AppendLiteral called: {The num is }Appended the literal stringAppendFormatted called: {10} is of type System.Int32
LogInterpolatedString
CustomInterpolatedStringHandler with limit:12
The num is

更多自定义可以参考默认的 DefaultInterpolatedStringHandler

使用自定义的 InterpolatedStringHandler 时,如果是结构体,参数建议使用 ref 引用传递,可以参考 https://github.com/dotnet/runtime/issues/57538

More

有哪些场景可以用呢?下面就是一个示例,更多细节可以参考:https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs

https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs#L280

[Conditional("DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ref AssertInterpolatedStringHandler message) =>Assert(condition, message.ToStringAndClear());

当然不仅于此,还有很多细节可以去挖掘,还有 StringBuilder/Memory 等也使用了新的方式来处理插值字符串

最后如果我们可以使用插值字符串,就尽可能地使用插值字符串来处理,从 .NET 6 以后就不会有装箱的问题了,性能还会更好

感兴趣的小伙伴们可以更加深入研究一下,上面的示例有需要的可以从 Github 上获取 https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp10Sample/InterpolatedStringSample.cs

References

  • https://github.com/dotnet/runtime/issues/50635

  • https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/InterpolatedStringHandlerAttribute.cs

  • https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md

  • https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/

  • https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs

  • https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated#compilation-of-interpolated-strings

  • https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.defaultinterpolatedstringhandler?view=net-6.0

  • https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/interpolated-string-handler

  • https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/improved-interpolated-strings

  • https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation

  • https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs

  • https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/FormattableString.cs

  • https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/FormattableStringFactory.cs

  • https://github.com/dotnet/runtime/issues/57538

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp10Sample/InterpolatedStringSample.cs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电影院为何有散落的青瓜?

1 摄像头&#xff1a;你套个袋子我就认不出来了吗&#xff1f;▼2 路边惊现大熊猫&#xff01;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 好一个驼小姐&#xff08;via.豆瓣社死小组满杯草莓&#xff09;▼4 学弟的理解也不是无迹可寻▼5 台湾人过生日的方…

【ACM】nyoj_305_表达式求值_201308081018

表达式求值时间限制&#xff1a;3000 ms | 内存限制&#xff1a;65535 KB 难度&#xff1a;3描述 Dr.Kong设计的机器人卡多掌握了加减法运算以后&#xff0c;最近又学会了一些简单的函数求值&#xff0c;比如&#xff0c;它知道函数min(20,23)的值是20 &#xff0c;add(10,98…

HDU 4267 A Simple Problem with Integers [树状数组]

根据%ka中a和k的不同组合建立55棵树状数组&#xff0c;每次修改操作只对其中1棵树状数组进行操作&#xff0c;每次查询对其中10棵树状数组统计增量和。 1 #include <string.h>2 #include <stdio.h>3 #define MAXN 500054 int n,q,x[MAXN];5 int ta,tb,cc,k,op;6 in…

字符串之旋转词

题目: 如果一个字符串str,把字符串str前面任意的部分挪到后面形成的字符串叫做str的旋转词。比如str="12345",str的旋转词有"12345"、"23451"、"34512"、"45123"、和"51234"。给定两个字符串a和b,请判断a和b…

Ecshop:后台添加新功能栏目以及管理权限设置

一、添加菜单项打开 /admin/includes/inc_menu.php文件(后台框架左边菜单)&#xff0c;在最后添加一行如下&#xff1a;1$modules[17_other_menu][sns_list] sns_list.php?actlist;ecshop默认一级菜单项有16项&#xff0c;所里这里的例子从17开始。当然这个key可以随便取的&a…

年轻人不讲武德有多可怕?

1 孩子&#xff0c;走你&#xff01;▼2 这都是什么阴间燃料&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 年轻人不讲武德&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 当你偷瞄喜欢的男生▼5 贝多芬&#xff1a;我入土这么都多年了&#xff08;…

使用C#体验函数式编程之——Partial application(局部应用)

函数式编程函数式编程是一种编程范式&#xff0c;着力于避免共享状态、可变数据和副作用产生&#xff0c;函数是第一等公民。这与面向对象编程相反&#xff0c;在面向对象编程中&#xff0c;应用程序状态通常与对象中的方法共享和共存。函数式编程是声明式的而不是命令式的&…

poj 1860 Currency Exchange (最短路bellman_ford思想找正权环 最长路)

感觉最短路好神奇呀&#xff0c;刚开始我都 没想到用最短路 题目&#xff1a;http://poj.org/problem?id1860 题意&#xff1a;有多种从a到b的汇率&#xff0c;在你汇钱的过程中还需要支付手续费&#xff0c;那么你所得的钱是 money&#xff08;nowmoney-手续费&#xff09;*r…

如何解决Maven依赖本地仓库eclipse报错的问题

一、应用场景 为了使用maven强大的包依赖管理和项目管理功能&#xff0c;故在项目中使用maven2作为项目建构工具。但是我的项目在内网构建&#xff0c;为了能使用maven2&#xff0c;只能使用本地仓库依赖策略。但是&#xff0c;有时候明明本地仓库确确实实存在相应的依赖包&…

windows socket 简单实例

Windows下Socket编程主要包括以下几部分&#xff1a;服务端 1、初始化Windows Socket库。 2、创建Socket。 3、绑定Socket。 4、监听。 5、Accept。 6、接收、发送数据。客户端 1、初始化Windows Socket库。 2、创建Socket。 3、连接Socket。 4、接收、发送…

如何将 image 转成 base64 字符串?

咨询区 vaj90我的项目中有一个需求&#xff0c;需要将 base64 格式的字符串 data:image/gif;base64,/9j/4AAQSkZJRgABAgEAYABgAAD.. 转成 image 格式&#xff0c;请问我该如何实现&#xff1f;回答区 Patashu&#xff1a;我写了一个 image 和 base64 相互转换的帮助类&#xff…

oracle执行命令显示2,Oracle数据库执行脚本常用命令小结

1. 执行一个SQL脚本文件复制代码 代码如下:sqlplus user/passservicename或复制代码 代码如下:SQL>start file_names或复制代码 代码如下:SQL> file_name我们可以将多条sql语句保存在一个文本文件中&#xff0c;这样当要执行这个文件中的所有的sql语句时&#xff0c;用上…

史上最强的烧脑合集!能全都搞懂的只有天才!

▲ 点击查看牛津大学&#xff0c;全球学生削尖脑袋想进的地方。它真正吸引人的&#xff0c;不仅是雄厚的师资、精湛的学术&#xff0c;更因为立校800余年来&#xff0c;始终有一套经典、独门的“高智商训练”方法&#xff01;25位首相、53位总统、11位国王、47位诺贝尔奖得主&a…

github上面如何编辑README

github上面如何编辑README 今天把自己做好的 《手机摇一摇震动刷新(支持ListView、GridView、Webview)》上传到github,之前代码都上传好了,就差介绍了 第一步:找到READM.md文件进入编辑状态 找到了READM.md文件,修改一番以后,我想试下水,先提交看看,如…

学习使用新浪接口随笔(一)

小生初入C#领域&#xff0c;因为学校部门里面在办新浪微博活动的时候&#xff0c;需要统计转发数量。&#xff08;个别团或者班级会去买转发数量&#xff0c;这没节操的&#xff09;&#xff0c;所以有的时候统计起来的时候&#xff0c;会比较麻烦。于是就产生了想利用新浪的AP…

安装office时,提示某项注册表无法写入,请查看是否有管理员权限

安装office时&#xff0c;提示某项注册表无法写入&#xff0c;请查看是否有管理员权限 大概就是这个意思&#xff0c;记不清楚了&#xff0c;解决办法&#xff1a; 首先如果你确认软件没有问题并且是用管理员帐户登录的话&#xff0c;请关闭防火墙与杀毒软件&#xff0c;重试&a…

开源Winform控件库:花木兰控件库

微信好友推荐&#xff0c;挺好看的Winfrom控件库&#xff0c;下面来看看。花木兰控件库Gitee截图介绍基于 C#&#xff08;语言&#xff09; 4.0 、 VS2019 、 Net Framework 4.0(不包括Net Framework 4.0 Client Profile) 开发的Winform控件库。为了兼容性采用了C#&#xf…

c语言题中的一些陷阱

1、求下列两个数组的长度关系characX[] "abcdefg";char acY[] { a, b,c, d, e, f, g };解析&#xff1a;对于字符串来说&#xff0c;字符串结尾默认为‘\0’&#xff0c;所以acX[]的长度为8&#xff0c;而第二个数组的大小长度为7&#xff0c;而且因为没有用‘\0’结…

“爱因斯坦兄弟”事件轰动纽约时报!双胞胎乱写博士论文,整容后越黑越红,竟然名利双收..........

全世界只有3.14 % 的人关注了爆炸吧知识只要够大就够震撼超模君前几天发了一篇文章《2020年最奇葩论文&#xff0c;打假网站看了都无语&#xff0c;原来外国人更擅长中医》&#xff0c;里面讲到的那篇奇葩论文&#xff0c;可真是让我见识到了什么叫做一本正经胡说八道&#xff…

oracle分区exchange,oracle 分区表exchange原理

oracle分区的exchange操作非常快&#xff0c;那原理是什么呢&#xff1f;下面我们来做个实验&#xff1a;SQL> create table test (id number(3));表已创建。SQL> insert into test values (1);已创建 1 行。SQL> commit;提交完成。SQL> select object_id,data_obj…