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 台湾人过生日的方…

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;应用程序状态通常与对象中的方法共享和共存。函数式编程是声明式的而不是命令式的&…

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

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

github上面如何编辑README

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

开源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…

Android之手机摇一摇震动刷新(支持ListView、GridView、WebView)并生成我的二维码

手机摇一摇震动刷新 第一步:看摇一摇震动刷新的前后的效果图 1、摇一摇震动刷新前的效果图如下(图片太大了,分2次录屏的,如果看着不爽请猛戳我的github上面的演示) 2、摇一摇震动刷新之后的效果图如下 说明:我笔记本的虚拟机没有重力感应,所以用虚拟机摇一摇没反应…

搭建基于虚拟账号的电子邮件系统

使用postfix&#xff0c;cyrus-sasl&#xff0c;courier-authlib&#xff0c;mysql&#xff0c;dovecot&#xff0c;extmail&#xff0c;extman搭建电子邮件系统&#xff0c;可以使用web方式以虚拟账号注册登录邮箱&#xff0c;并且发送接受邮件&#xff0c;实现电子邮件功能。…

第一题

转载于:https://www.cnblogs.com/bilibiliganbei/p/5251796.html

此内容过于真实,引起强烈舒适

全世界只有3.14 % 的人关注了爆炸吧知识有这么一群人&#xff0c;年纪轻轻却有着远超同龄人或者常人的思想跟经验。其实&#xff0c;思想深度和年龄的关系并不大。饱读诗书的人&#xff0c;自然见识不凡&#xff0c;思考的内容也比别人更深刻。随着经历和阅历的增加&#xff0c…

Win11重磅新功能推送!

微软宣布现已开始向 Windows 11 正式版用户推出全新表情符号&#xff08; emoji &#xff09;&#xff0c;被称为“大眼夹”的经典 Office 助手&#xff08;Clippy&#xff09;现已回归&#xff01;全新表情符号基于流畅设计&#xff08;Fluent Design&#xff09;&#xff0c;…

话里话外:装备制造业企业迫切需要实现信息化综合集成

博主推荐延展咨询资深顾问 沈靓文章 装备制造企业产品具有单件产值大、技术含量高、生产周期长、完全个性化生产的特点&#xff0c;伴随着制造业整体水平的提高&#xff0c;近几年又兴起了异地制造、移动工厂等模式&#xff0c;而装备制造业现有的管理方式和信息化水平多不能适…

日本艺伎的拍照姿势,竟源于孔子?看完真的涨知识···

全世界只有3.14 % 的人关注了爆炸吧知识本文授权转载自公众号&#xff1a;艺非凡&#xff08;ID&#xff1a;efifan&#xff09;相信很多人都见过下面这个emoji表情&#xff1a;????????????没错&#xff0c;在风靡全球20余年的美剧《老友记》中&#xff0c;Ross决…

程序员如何把控自己的职业

这篇文章的主要内容主要是我今年3月份在腾讯做的直播&#xff0c;主要是想让一些技术人员对世界有一个大体的认识&#xff0c;并且在这个认识下能够有一个好的方法成就自己。而不是在一脸蒙圈的状态下随波逐流&#xff0c;而日益迷茫和焦虑。直播完后&#xff0c;腾讯方面把我的…

Spring学习笔记十二---泛型依赖注入

为什么80%的码农都做不了架构师&#xff1f;>>> package generic_di;import org.springframework.stereotype.Repository;Repository public class BaseRepository<T> {public void save() {System.out.println("Repository save ...");} }package…

女孩子狠起来可以多可怕?

1 让我康康是什么好吃的&#xff1f;▼2 抢答&#xff1a;他们到底能不能走出去&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 小朋友的眼睛是雪亮的&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 这该死的魅力▼5 我脚伤了&#xff0…

jquery mobile页面切换效果(Flip toggle switch)(注:jQuery移动使用的数据属性的列表。 )...

1.页面切换&#xff08;data-transition&#xff09; 地址&#xff1a;http://api.jquerymobile.com/data-attribute/ data-transition fade | flip | flow | pop | slide | slidedown | slidefade | slideup | turn | none 褪色 | |翻转||流流行幻灯片| | | slidedown | s…