最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#利用插值字符串處理器寫一個(gè)sscanf

 更新時(shí)間:2025年02月17日 08:21:48   作者:hez2010  
這篇文章主要為大家詳細(xì)介紹了C#如何利用插值字符串處理器寫一個(gè)sscanf,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

前言

什么?用 C# 插值字符串處理器寫一個(gè)輸入用的 sscanf?你確定不是輸出用的 sprintf?

我猜不少讀者看到標(biāo)題后大概會(huì)有上述的想法。然而我們這里還真就是實(shí)現(xiàn) sscanf,而不是 sprintf。

插值字符串處理器

C# 有一個(gè)特性叫做插值字符串,使用插值字符串,你可以自然地往字符串里面插入變量的值,比如:$"abc{x}def",這一改以往通過 string.Format 來格式化字符串的方式,使得不再需要先傳遞一個(gè)字符串模板再挨個(gè)傳遞參數(shù),非常方便。

在插值字符串的基礎(chǔ)上更進(jìn)一步,C# 支持插值字符串處理器,意味著你可以自定義字符串的插值行為。比如一個(gè)簡(jiǎn)單的例子:

[InterpolatedStringHandler]
struct Handler(int literalLength, int formattedCount)
{
    public void AppendLiteral(string s)
    {
        Console.WriteLine($"Literal: '{s}'");
    }

    public void AppendFormatted<T>(T v)
    {
        Console.WriteLine($"Value: '{v}'");
    }
}

在使用的時(shí)候,只需要把傳遞 string 參數(shù)的地方都換成這個(gè) Handler 類型,就能做到按照你自定義的方式來處理插值字符串,我們的插值字符串會(huì)被 C# 編譯器自動(dòng)變換成 Handler 的構(gòu)造和調(diào)用然后被傳入:

void Foo(Handler handler) { }
var x = 42;
Foo($"abc{x}def");

比如上面這個(gè)例子,你會(huì)得到輸出:

Literal: 'abc'
Value: '42'
Literal: 'def'

這大大方便了各種結(jié)構(gòu)化日志框架的處理,你只需要簡(jiǎn)單的把插值字符串傳遞進(jìn)去,日志框架就能根據(jù)你插值的方式來做到結(jié)構(gòu)化解析,從而完全避免了手動(dòng)去格式化字符串。

帶參數(shù)的插值字符串處理器

其實(shí) C# 的插值字符串處理器還支持帶額外的參數(shù):

[InterpolatedStringHandler]
struct Handler(int literalLength, int formattedCount, int value)
{
    public void AppendLiteral(string s)
    {
        Console.WriteLine($"Literal: '{s}'");
    }

    public void AppendFormatted<T>(T v)
    {
        Console.WriteLine($"Value: '{v}'");
    }
}

void Foo(int value, [InterpolatedStringHandlerArgument("value")] Handler handler) { }
Foo(42, $"abc{x}def");

這么一來,42 就會(huì)被傳入 handler 的 value 參數(shù)當(dāng)中,這允許我們捕獲來自調(diào)用方的上下文,畢竟在日志場(chǎng)景中,根據(jù)不同參數(shù)來決定不同的格式很常見。

sscanf?

眾所周知 C/C++ 里面有一個(gè)很常用的函數(shù) sscanf,它接受一個(gè)文本輸入和一個(gè)格式化模板,然后再傳遞對(duì)格式化部分的變量的引用,就能把變量的值解析出來:

const char* input = "test 123 test";
const char* template = "test %d test";
int v = 0;
sscanf(input, template, &v);
printf("%d\n", v); // 123

那我們能不能在 C# 里復(fù)刻一個(gè)呢?當(dāng)然可以!只不過需要一點(diǎn)點(diǎn)黑魔法。

用 C# 實(shí)現(xiàn) sscanf

首先我們做一個(gè)帶參數(shù)的插值字符串處理器:

[InterpolatedStringHandler]
ref struct TemplatedStringHandler(int literalLength, int formattedCount, ReadOnlySpan<char> input)
{
    private ReadOnlySpan<char> _input = input;

    public void AppendLiteral(ReadOnlySpan<char> s)
    {
    }

    public void AppendFormatted<T>(T v) where T : ISpanParsable<T>
    {
    }
}

這里我們把所有的 string 都換成 ReadOnlySpan<char> 減少分配。

按照 sscanf 的使用方法,我們按理來說應(yīng)該做成類似這樣的東西:

void sscanf(ReadOnlySpan<char> input, ReadOnlySpan<char> template, params object[] args);

但是很顯然,這里我們需要的是 (ref object)[],因?yàn)槲覀冃枰獋鬟f引用進(jìn)去才能做到對(duì)外部變量的更新,而不是直接把變量的值當(dāng)作 object 傳進(jìn)去。那怎么辦呢?

你會(huì)發(fā)現(xiàn),C# 的插值字符串處理器里已經(jīng)包含了各變量的值,因此我們完全不需要像 C/C++ 那樣通過類似 %d 之類的占位符來插入變量!相對(duì)于 "test %d test" 我們可以直接寫 $"test {v} test",然后通過引用傳遞這個(gè) v。

一個(gè)很自然的想法是,我們把只需要把 AppendFormatted<T>(T v) 改成 AppendFormatted<T>(ref T v) 不就行了。

然而實(shí)際這么操作之后你會(huì)發(fā)現(xiàn)這么做是行不通的:

[InterpolatedStringHandler]
ref struct TemplatedStringHandler(int literalLength, int formattedCount, ReadOnlySpan<char> input)
{
    private ReadOnlySpan<char> _input = input;

    public void AppendLiteral(ReadOnlySpan<char> s)
    {
    }

    public void AppendFormatted<T>(ref T v) where T : ISpanParsable<T>
    {
    }
}

void sscanf(ReadOnlySpan<char> input, [InterpolatedStringHandlerArgument("input")] TemplatedStringHandler template);

當(dāng)我們?cè)噲D調(diào)用 sscanf 的時(shí)候:

int v = 0;
sscanf("test 123 test", $"test {ref v} test"); // error CS1525: Invalid expression term 'ref'

報(bào)錯(cuò)了!插值字符串的值部分里寫 ref 關(guān)鍵字是無效的!

注意到這個(gè)錯(cuò)誤是來自 C# 編譯器的 parser,也就是說只要我們從語法上把這個(gè) ref 干掉,那就能通過編譯了。

此時(shí)我們靈機(jī)一動(dòng),我們 C# 不是有 in 來傳遞只讀引用嗎?C# 對(duì)于 in 傳遞只讀引用會(huì)自動(dòng)幫我們創(chuàng)建引用并傳遞進(jìn)去,無需在語法上顯式指定 ref,于是我們稍微利用一下這個(gè)特性改造一番:

[InterpolatedStringHandler]
ref struct TemplatedStringHandler(int literalLength, int formattedCount, ReadOnlySpan<char> input)
{
    private ReadOnlySpan<char> _input = input;

    public void AppendLiteral(ReadOnlySpan<char> s)
    {
    }

    public void AppendFormatted<T>(in T v) where T : ISpanParsable<T>
    {
    }
}

然后就會(huì)發(fā)現(xiàn),下面這個(gè)代碼可以成功編譯了:

int v = 0;
sscanf("test 123 test", $"test {v} test");

此時(shí)我們離成功只剩下最后一步:傳遞進(jìn)來的是只讀引用,可是為了提取出變量我們需要更新引用的值,怎么辦呢?

好在我們有 Unsafe.AsRef 把只讀引用轉(zhuǎn)換成可變引用,那最后一個(gè)問題解決了,我們就可以開始我們的實(shí)現(xiàn)了。

[InterpolatedStringHandler]
ref struct TemplatedStringHandler(int literalLength, int formattedCount, ReadOnlySpan<char> input)
{
    private int _index = 0;
    private ReadOnlySpan<char> _input = input;

    public void AppendLiteral(ReadOnlySpan<char> s)
    {
        var offset = Advance(0); // 先跳過連續(xù)空白字符
        _input = _input[offset..];
        _index += offset;
  
        if (_input.StartsWith(s)) // 從輸入字符串中去掉模板字符串的非變量部分
        {
            _input = _input[s.Length..];
        }
        else throw new FormatException($"Cannot find '{s}' in the input string (at index: {_index}).");

        _index += s.Length;
        literalLength -= s.Length;
    }

    public void AppendFormatted<T>(in T v) where T : ISpanParsable<T>
    {
        var offset = Advance(0); // 先跳過連續(xù)空白字符
        _input = _input[offset..];
        _index += offset;

        var length = Scan(); // 計(jì)算到下一個(gè)空白字符為止的長(zhǎng)度
        if (T.TryParse(_input[..length], null, out var result)) // 解析!
        {
            Unsafe.AsRef(in v) = result; // 把只讀引用換成可變引用后更新引用值
            _input = _input[length..];
            _index += length;
            formattedCount--;
        }
        else
        {
            throw new FormatException($"Cannot parse '{_input[..length]}' to '{typeof(T)}' (at index: {_index}).");
        }
    }

    // 向后掃描,直到遇到空白字符停止
    private int Scan()
    {
        var length = 0;
        for (var i = 0; i < _input.Length; i++)
        {
            if (_input[i] is ' ' or '\t' or '\r' or '\n') break;
            length++;
        }
        return length;
    }

    // 跳過所有的空白字符
    private int Advance(int start)
    {
        var length = start;
        while (length < _input.Length && _input[length] is ' ' or '\t' or '\r' or '\n')
        {
            length++;
        }
        return length;
    }
}

然后我們提供一個(gè) sscanf 暴露我們的插值字符串處理器即可:

static void sscanf(ReadOnlySpan<char> input, [InterpolatedStringHandlerArgument("input")] TemplatedStringHandler template) { }

使用

int x = 0;
string y = "";
bool z = false;
DateTime d = default;
sscanf("test 123 hello false 2025/01/01T00:00:00 end", $"test{x}{y}{z}wppm3vysvbpend");
Console.WriteLine(x);
Console.WriteLine(y);
Console.WriteLine(z);
Console.WriteLine(d);

得到輸出:

123
hello
False
2025年1月1日 0:00:00

而 scanf 只不過是 sscanf(Console.ReadLine(), template) 的簡(jiǎn)寫罷了,所以這里我們有 sscanf 就完全足夠了。

結(jié)論

C# 的插值字符串處理器非常強(qiáng)大,利用這個(gè)特性,我們成功實(shí)現(xiàn)了比 C/C++ 中 sscanf 還要更好用的多的字符串解析函數(shù),不僅不需要格式化字符串占位,還能自動(dòng)推導(dǎo)類型,甚至連在后面的參數(shù)里逐個(gè)傳遞變量引用的需要都直接省掉了,在此基礎(chǔ)上我們還做到了零分配。

到此這篇關(guān)于C#利用插值字符串處理器寫一個(gè)sscanf的文章就介紹到這了,更多相關(guān)C#插值字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#中IEnumerable、ICollection、IList、List之間的區(qū)別

    C#中IEnumerable、ICollection、IList、List之間的區(qū)別

    IEnumerable、ICollection、IList、List之間的區(qū)別,本文分別分析了它的實(shí)現(xiàn)源碼,從而總結(jié)出了它們之間的關(guān)系和不同之處。對(duì)C# IEnumerable、ICollection、IList、List相關(guān)知識(shí),感興趣的朋友一起看看吧
    2021-07-07
  • Unity?制作一個(gè)分?jǐn)?shù)統(tǒng)計(jì)系統(tǒng)

    Unity?制作一個(gè)分?jǐn)?shù)統(tǒng)計(jì)系統(tǒng)

    項(xiàng)目中經(jīng)常遇到分?jǐn)?shù)統(tǒng)計(jì)的需求,例如操作正確則計(jì)分,相反則不計(jì)分失去該項(xiàng)分?jǐn)?shù),為了應(yīng)對(duì)需求需要一個(gè)分?jǐn)?shù)統(tǒng)計(jì)系統(tǒng)。本文主要介紹了通過Unity實(shí)現(xiàn)這樣的一個(gè)計(jì)分系統(tǒng),快來跟隨小編一起學(xué)習(xí)吧
    2021-12-12
  • C# Windows API應(yīng)用之基于FlashWindowEx實(shí)現(xiàn)窗口閃爍的方法

    C# Windows API應(yīng)用之基于FlashWindowEx實(shí)現(xiàn)窗口閃爍的方法

    這篇文章主要介紹了C# Windows API應(yīng)用之基于FlashWindowEx實(shí)現(xiàn)窗口閃爍的方法,結(jié)合實(shí)例形式分析了Windows API函數(shù)FlashWindowEx的功能、定義及實(shí)現(xiàn)窗口閃爍的相關(guān)技巧,需要的朋友可以參考下
    2016-08-08
  • C#使用ffmpeg實(shí)現(xiàn)將圖片保存為mp4視頻

    C#使用ffmpeg實(shí)現(xiàn)將圖片保存為mp4視頻

    FFmpeg是一個(gè)開源的跨平臺(tái)多媒體處理工具,它提供了強(qiáng)大的功能,包括頻和視頻編碼、解碼、轉(zhuǎn)碼等,本文我們將使用FFmpeg實(shí)現(xiàn)將圖片保存為mp4視頻,感興趣的可以了解下
    2024-11-11
  • C#中虛方法virtual示例詳解

    C#中虛方法virtual示例詳解

    這篇文章主要介紹了C#中虛方法virtual的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2025-03-03
  • 一些C#常見面試題目以及答案總結(jié)

    一些C#常見面試題目以及答案總結(jié)

    這篇文章主要介紹了C#常見面試題目以及答案的相關(guān)資料,分別是C#編程語言中的委托、多線程、事件、異常處理、異步編程、LINQ、內(nèi)存管理、圖像處理、內(nèi)存管理、設(shè)計(jì)模式等概念和實(shí)現(xiàn)方法,需要的朋友可以參考下
    2025-03-03
  • C#中實(shí)現(xiàn)比較兩個(gè)對(duì)象是否相等的7個(gè)方法總結(jié)

    C#中實(shí)現(xiàn)比較兩個(gè)對(duì)象是否相等的7個(gè)方法總結(jié)

    在 C# 中,要比較兩個(gè)對(duì)象實(shí)例是否相等,有一些常用的方法,比如實(shí)現(xiàn) IEquatable<T> 接口、重寫 Object.Equals 方法,或使用自定義比較邏輯等等,本文為大家總結(jié)了7 種常用的方法,大家可以根據(jù)需要進(jìn)行選擇
    2025-11-11
  • C#中隊(duì)列排序的實(shí)踐方法

    C#中隊(duì)列排序的實(shí)踐方法

    本文主要介紹了C#中隊(duì)列排序的實(shí)踐方法,通過創(chuàng)建自定義優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)排序,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-05-05
  • 解析C#編程的通用結(jié)構(gòu)和程序書寫格式規(guī)范

    解析C#編程的通用結(jié)構(gòu)和程序書寫格式規(guī)范

    這篇文章主要介紹了C#編程的通用結(jié)構(gòu)和程序書寫格式規(guī)范,這里我們根據(jù)C#語言的開發(fā)方微軟給出的約定來作為編寫樣式參照,需要的朋友可以參考下
    2016-01-01
  • 客戶端實(shí)現(xiàn)藍(lán)牙接收(C#)知識(shí)總結(jié)

    客戶端實(shí)現(xiàn)藍(lán)牙接收(C#)知識(shí)總結(jié)

    網(wǎng)上有關(guān)藍(lán)牙接收的資料很多,使用起來也很簡(jiǎn)單,但是我覺得還是有必要把這些知識(shí)總結(jié)下來,藍(lán)牙開發(fā)需要用到一個(gè)第三方的庫InTheHand.Net.Personal.dll,感興趣的朋友可以了解下,或許對(duì)你有所幫助
    2013-02-02

最新評(píng)論

德州市| 贵溪市| 武隆县| 和平县| 博白县| 略阳县| 托里县| 漯河市| 双流县| 瑞昌市| 南安市| 潼关县| 大港区| 定襄县| 高要市| 平南县| 黎川县| 盘锦市| 富源县| 扎囊县| 浦城县| 呼玛县| 尚义县| 饶阳县| 太谷县| 德阳市| 安乡县| 许昌市| 峨边| 石首市| 赣榆县| 新邵县| 隆化县| 宝应县| 乐业县| 页游| 康平县| 海林市| 沭阳县| 辽宁省| 东光县|