.NET6新东西--插值字符串优化

简介: .NET6新东西--插值字符串优化

字符串是我们平时使用最多的一个类型,从C#6开始就支持插值字符串,方便我们进行字符串的操作,并且大部分分析器也推荐使用插值这种写法,因为它够使得我们的代码更加清晰简洁,到了.NET6中的C#10则为我们提供了更好的实现方式以及更佳的性能。


那么什么是插值字符串呢?它是以$符开头的,类似于 $“Hello {name}” 这样的字符串,下面的例子是插值字符串的简单使用:

var name = "插值字符串";
var hello = $"你好 {name}!";
var num= 10;
var numMessage= $"我喜欢数字 {num}";

我们不需要使用format就可以直接简化字符串拼接,并且对于一些简单的字符串拼接可以简化成string.Concat,在.NET6之前的版本中它会被翻译成低版本C#中的string.Format形式,上述代码翻译成低版本C# 代码如下所示:

string name = "插值字符串";
string hello = string.Concat("你好 ", name, "!");
int num= 10;
string numMessage= string.Format("我喜欢数字 {0}", );

对于string.Format来说,如果参数是值类型会发生装箱,变为 object,这一点我们可以通过IL代码看出。这里需要注意的是插值字符串格式化的时候会使用当前的CultureInfo,如果我们需要使用不同的CultureInfo或手动指定CultureInfo,那么可以使用FormattableString或FormattableStringFactory来实现。代码如下会根据指定的CultureInfo显示出不同的数字格式:

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

在.NET6 中本文的第一段代码会翻译成生成下面这样的:

string name = "插值字符串";
string hello = string.Concat ("Hello ", name, "!");
int num= 10;
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(11, 1);
defaultInterpolatedStringHandler.AppendLiteral("我喜欢数字 ");
defaultInterpolatedStringHandler.AppendFormatted(num);
string numDesc = defaultInterpolatedStringHandler.ToStringAndClear();

在.NET6中会由DefaultInterpolatedStringHandler处理插值字符串。它DefaultInterpolatedStringHandler是结构体,并且包含泛型方法AppendFormatted来避免装箱操作,这样它在format的时候性能更好。并且在.NET6中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();

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


  1. 构造函数至少需要两个int参数,一个是字符串中常量字符的长度,一个是需要格式化的参数的数量;
  2. 需要具有public的AppendLiteral(string s)方法处理常量字符的拼接;
  3. 需要具有public的AppendFormatted(T t)方法处理参数;
  4. 自定义处理器需要使用InterpolatedStringHandler标记,并且处理器可以是class也可以是struct。下面的代码就实现了一个简单的插值字符串处理器
[InterpolatedStringHandler]
public struct CustomInterpolatedStringHandler
{
    private readonly StringBuilder builder;
    public CustomInterpolatedStringHandler(int literalLength, int formattedCount)
    {
        builder = new StringBuilder(literalLength);
    }
    public void AppendLiteral(string s)
    {
        builder.Append(s);
    }
    public void AppendFormatted<T>(T t)
    {
        builder.Append(t?.ToString());
    }
    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());
}
LogInterpolatedString("我喜欢的数字是10");
int num=20;
LogInterpolatedString($"我喜欢的数字是{num}");

输出结果如下:

LogInterpolatedString
我喜欢的数字是10
LogInterpolatedString
CustomInterpolatedStringHandler
我喜欢的数字是20

我们还可以在自定义的插值字符串处理器的构造器中增加自定义参数,使用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);
        _limit = limit;
    }
    public void AppendLiteral(string s)
    {
        builder.Append(s);
    }
    public void AppendFormatted<T>(T t)
    {
        if (t is int n && n < _limit)
        {
            return;
        }
        builder.Append(t?.ToString());
    }
    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, $"我喜欢的数字是{num}");
Console.WriteLine();
LogInterpolatedString(15, $"我喜欢的数字是{num}");

输出结果如下:

LogInterpolatedString
CustomInterpolatedStringHandler with limit:10
我喜欢的数字是10
LogInterpolatedString
CustomInterpolatedStringHandler with limit:15
我喜欢的数字是

从上面输出的结果可以看出第一次打印出来了 num,第二次没有打印 num。

其实还有一个特殊的参数,我们可以在构造方法中引入一个bool类型的out参数,如果值为false则不会进行字符串的拼接,我们再次改造一下前面的代码:

public CustomInterpolatedStringHandler(int literalLength, int formattedCount, int limit, out bool shouldAppend)
{
    shouldAppend = limit < 30;
    builder = new StringBuilder(shouldAppend ? literalLength : 0);
    _limit = limit;
}

当limit参数小于30时进行字符串的拼接,否则就不输出,调用代码修改如下:

LogInterpolatedString(10, $"我喜欢的数字是 {num}");
Console.WriteLine();
LogInterpolatedString(15, $"我喜欢的数字是{num}");
Console.WriteLine();
LogInterpolatedString(30, $"我喜欢的数字是{num}");

输出结果是这样的

LogInterpolatedString
CustomInterpolatedStringHandler with limit:10
我喜欢的数字是10
LogInterpolatedString
CustomInterpolatedStringHandler with limit:15
我喜欢的数字是
LogInterpolatedString
CustomInterpolatedStringHandler with limit:30

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

目录
相关文章
|
6月前
|
开发框架 .NET
.net 字符串逗号隔开去重
.net 字符串逗号隔开去重
28 0
.net 字符串逗号隔开去重
|
10月前
|
API
.NET指定图片地址下载并转换Base64字符串
.NET指定图片地址下载并转换Base64字符串
|
JSON 数据格式
将JSON字符串反序列化为指定的.NET对象类型
将JSON字符串反序列化为指定的.NET对象类型
142 0
.NET中将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),并使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA
.NET中将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),并使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA
235 0
|
JSON 开发框架 算法
.Net 序列化枚举为字符串
我所做的项目是需要调用业务算法的,算法中有一个入参是油品的性质,这个性质有名称、编码、类型等属性,其中类型是固定质量性质、体积性质和其他性质这三种,所以我把其作为枚举类型。问题也由此产生,默认情况下,枚举是以其整数形式进行 JSON 序列化,这就需要同研发算法的同事约定好数值的含义。但是经过协商,算法同事要求我们传递成字符串。因此,我们希望它们在一些情况下以字符串的形式进行序列化。本文将讲解实现这一目标的各种方法。
|
前端开发 程序员
.NET-记一次架构优化实战与方案-目录
.NET-记一次架构优化实战与方案-目录
99 0
|
消息中间件 存储 设计模式
.NET-记一次架构优化实战与方案-底层服务优化
.NET-记一次架构优化实战与方案-底层服务优化
111 0
.NET-记一次架构优化实战与方案-底层服务优化
|
SQL 存储 前端开发
.NET-记一次架构优化实战与方案-前端优化
.NET-记一次架构优化实战与方案-前端优化
107 0
.NET-记一次架构优化实战与方案-前端优化
|
前端开发 程序员
.NET-记一次架构优化实战与方案-梳理篇
.NET-记一次架构优化实战与方案-梳理篇
97 0
.NET-记一次架构优化实战与方案-梳理篇
|
运维 监控 API
.net core实践系列之短信服务-架构优化(二)
.net core实践系列之短信服务-架构优化(二)
163 0