Closed ufcpp closed 2 years ago
https://gist.github.com/ufcpp/233d33b0220215bb5bdf2a10e8a434bf#file-benchmark-cs
手元の結果:
Method | Mean | Error | StdDev | Gen 0 | Allocated |
---|---|---|---|---|---|
OldStyle | 935.3 us | 2.75 us | 2.58 us | 229.4922 | 1,875 KB |
Improved | 565.5 us | 1.77 us | 1.57 us | 46.8750 | 391 KB |
InvariantCulture | 562.9 us | 1.48 us | 1.24 us | 46.8750 | 391 KB |
InitialBuffer | 416.1 us | 0.90 us | 0.84 us | 47.3633 | 391 KB |
InitialBufferInvariantCulture | 418.3 us | 0.98 us | 0.87 us | 47.3633 | 391 KB |
InitialBufferSkipLocalsInitInvariantCulture | 406.9 us | 1.51 us | 1.41 us | 47.3633 | 391 KB |
InitialSingleBufferInvariantCulture | 404.2 us | 1.48 us | 1.32 us | 47.3633 | 391 KB |
// 多分リーク(ArrayPool への Return が掛からない)
var ss = $"{x} {m() ?? throw new Exception()}";
一応、現状の ArrayPool の実装上、 Return が掛からなくても、「毎度配列が new されて Pool の意味がない」以上のペナルティはないらしい。 new された配列の参照がなくなった時点で GC の対象にはなる。
var t = Task.Delay(1).ContinueWith(_ => 1);
string s = $"{x} / {await t} / {z}"; // これは string.Format になっちゃう。
var t = await Task.Delay(1).ContinueWith(_ => 1);
DefaultInterpolatedStringHandler s = $"{x} / {t} / {z}"; // これはそもそもコンパイルエラー。
var t = await Task.Delay(1).ContinueWith(_ => 1);
string s = $"{x} / {t} / {z}"; // これは大丈夫。AppendFormatted 出てる
// $"" はカルチャー依存
var x = 1234;
var y = 1.234;
var z = new DateOnly(2001, 2, 3);
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-fr");
// カルチャー依存を避けたければこれを使う。
var s = string.Create(
CultureInfo.InvariantCulture,
$"{x} / {y} / {z}");
Console.WriteLine(s);
//var s = string.Create(
// CultureInfo.InvariantCulture,
// $"{x} / {y} / {z}");
//
// ↓こう展開される
DefaultInterpolatedStringHandler h = new(6, 3, CultureInfo.InvariantCulture);
h.AppendFormatted(x);
h.AppendLiteral(" / ");
h.AppendFormatted(y);
h.AppendLiteral(" / ");
h.AppendFormatted(z);
// 展開、ここまで
var s = h.ToStringAndClear(); // string.Create の中はこれ1行。
Console.WriteLine(s);
//var s = string.Create(
// CultureInfo.InvariantCulture,
// stackalloc char[128],
// $"{x} / {y} / {z}");
//
// ↓こう展開される
DefaultInterpolatedStringHandler h = new(
6, 3,
CultureInfo.InvariantCulture,
stackalloc char[128]);
// ref がつかない struc なら間に await があっても平気
C.M($"{1} / {await Task.Delay(1).ContinueWith(_ => 2)} / {3}");
class C
{
//public static void M(string _) => Console.WriteLine("string");
public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
}
// これが $"" を受け取れる最低ラインのパターン。
[System.Runtime.CompilerServices.InterpolatedStringHandler]
public class DummyHandler
C.M("");
C.M($""); // string
C.M($"{""}"); // string
const string a = "aaa";
C.M($"{""} {a}"); // string
C.M($"{1}"); // DummyHandler
string b = "aaa";
C.M($"{b}"); // DummyHandler
int x = 1;
C.M($"{x}"); // DummyHandler
class C
{
public static void M(string _) => Console.WriteLine("string");
public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
}
using System.Runtime.CompilerServices;
var a = 1;
write<int>(new[] { "X", "Y", "Z" }, $"{1}{2}{a}");
// 素直にこうしろよという説はある。
//new Dictionary<string, object>
//{
// ["X"] = 1,
//}
static void write<TValue>(
string[] keys,
[InterpolatedStringHandlerArgument("keys")]
ParamsDictionaryHandler<TValue> handler)
{
var dic = handler.Dictionary;
foreach (var (key, value) in dic)
{
Console.WriteLine($"{key}: {value}");
}
}
[InterpolatedStringHandler]
public struct ParamsDictionaryHandler<T>
{
internal readonly Dictionary<string, T> Dictionary;
private readonly string[] _keys;
private int _i = 0;
public ParamsDictionaryHandler(int _, int formattedCount, string[] keys)
{
_keys = keys;
Dictionary = new(formattedCount);
}
public void AppendFormatted(T value)
{
Dictionary[_keys[_i]] = value;
_i++;
}
}
// M(string) をコメントアウトすると…
C.M(""); // ダメ。
// M(string) の有無で呼ばれる先が違う。
C.M($""); // OK。
C.M($"{"abc"}"); // OK。
class C
{
//public static void M(string _) => Console.WriteLine("string");
public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
}
C.M($""); // ダメ。
C.M($"{"abc"}"); // ダメ。
// キャスト挟めばOK。
C.M((DummyHandler)$""); // OK。
C.M((DummyHandler2)$"{"abc"}"); // OK。
// as とかはダメ。
C.M($"" as DummyHandler); // ダメ。
C.M((DummyHandler)""); // ダメ。
// IFormattable も同じようなもの。
var xx1 = (IFormattable)$""; // OK
var xx2 = $"" as IFormattable; // ダメ
class C
{
// わざとオーバーロード解決できなくする。
public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
public static void M(DummyHandler2 _) => Console.WriteLine("DummyHandler2");
}
using System.Text;
var sb = new StringBuilder();
sb.Append($"{1}"); // AppendInterpolatedStringHandler が呼ばれる。
var tw = new TextWriter(); // TextWriter には同様のオーバーロードはなさげ…
// 完全なアロケーション0フォーマット。
Span<char> buffer = stackalloc char[128];
buffer.TryWrite($"{1}.{2}", out var charWritten);
true invariant culture string interpolation は多少作りたい… ヤーポン、華氏、MM/dd/yyyy な国が invariant 騙るのやめろ。
Console.WriteLine(DateTime.Parse("平成35/1/1"));
Console.WriteLine(DateTime.Parse("昭和100/1/1"));
Console.WriteLine(DateTime.Parse("昭和2000/1/1"));
Console.WriteLine(DateTime.Parse("9999/1/1"));
// さすがにダメっぽい。
//Console.WriteLine(DateTime.Parse("平成元年1月1日"));
// FormatException
//Console.WriteLine(DateTime.Parse("昭和-1/1/1"));
// 以下の2つは範囲外だけど FormatException
//Console.WriteLine(DateTime.Parse("昭和9999/1/1"));
//Console.WriteLine(DateTime.Parse("10000/1/1"));
配信URL: https://youtu.be/P7-kykSvCmk
41 残作業その1。
https://twitter.com/ufcpp/status/1425773277834711046 https://gist.github.com/ufcpp/233d33b0220215bb5bdf2a10e8a434bf https://ufcpp.net/blog/2021/8/net6p7/#interpolated-string https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md
たぶん、これ単品で1配信になる。