字串拼接這個隱藏大坑,我表示不服~

gui.h發表於2022-05-13

前言

先看寫個簡單的程式碼,看看你能不能答對

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
string v1 = null;
string v2 = null;

var v3 = v1 + v2;

Console.WriteLine();

請問上面這段程式碼v3的值是什麼?

A:null

B:string.Empty

C:異常

請讀者好好思考一下再往下看~

答案

不墨跡,直接執行程式碼看結果:
image.png
很明顯答案是 B,此時你會不會有疑問:兩個null相加,怎麼會是""?我也有這個疑問,而且怎麼都想不明白為什麼~~~

解惑

將上面的程式碼編譯後,使用ILSpy反編譯工具檢視IL中間語言程式碼看看,如下:

.method private hidebysig static 
    void '<Main>$' (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Header size: 12
    // Code size: 30 (0x1e)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] string v1,
        [1] string v2,
        [2] string v3
    )

    // Console.WriteLine("Hello, World!");
    IL_0000: ldstr "Hello, World!"
    IL_0005: call void [System.Console]System.Console::WriteLine(string)
    // string text = null;
    IL_000a: nop
    IL_000b: ldnull
    IL_000c: stloc.0
    // string text2 = null;
    IL_000d: ldnull
    IL_000e: stloc.1
    // string text3 = text + text2;
    IL_000f: ldloc.0
    IL_0010: ldloc.1
    //++++++++++++++++++++++注意這一行++++++++++++++++++++++++++++
    IL_0011: call string [System.Runtime]System.String::Concat(string, string)
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    IL_0016: stloc.2
    // Console.WriteLine();
    IL_0017: call void [System.Console]System.Console::WriteLine()
    // }
    IL_001c: nop
    IL_001d: ret
} // end of method Program::'<Main>$'

主要看上面用註釋標記的那行

IL_0011: call string [System.Runtime]System.String::Concat(string, string)

由此可以知道我們的兩個變數相加,其實底層呼叫的是String::Concat(string, string)方法,從github上拉取dotnet/runtime倉庫原始碼,找到string型別的原始碼Concat(string, string)方法。

public static string Concat(string? str0, string? str1)
{
    // 第一個引數為空
    if (IsNullOrEmpty(str0))
    {
        // 第二個引數也為空
        if (IsNullOrEmpty(str1))
        {
            // 返回string.Empty
            return string.Empty;
        }
        return str1;
    }

    if (IsNullOrEmpty(str1))
    {
        return str0;
    }

    int str0Length = str0.Length;

    string result = FastAllocateString(str0Length + str1.Length);

    FillStringChecked(result, 0, str0);
    FillStringChecked(result, str0Length, str1);

    return result;
}

原始碼很簡單,一上來就找到了返回string.Empty的結果,至此我們知道它為什麼結果是string.Empty

大坑

之所以寫本文,確實是實際專案中因為兩個null字串相加與我想想的不一樣,出現bug,專案中的程式碼大概是這樣的:

// context.Error和context.ErrorDes均為string型別,
// 兩者絕不會存在為string.Empty的情況,但是可能同時為null
var resMsg = (context.Error + context.ErrorDes) ?? "系統異常"

本以為上面這段程式碼本意是想拼接兩個錯誤資訊輸出,如果兩個錯誤資訊都是null,那麼就返回系統異常,結果可想而知,context.Errorcontext.ErrorDes雖然均為null,但是他們的結果不是null,最終resMsg"",害~~~

思考

雖然我們知道為啥是string.Empty了,但是還是覺得null才更加合理,不知道設計者是出於什麼考慮,如果你知道,請告訴我,如果本文對你有幫助,請點贊,關注,轉發,支援一波~

相關文章