.NET 歡樂程式設計術之型別超級轉換之術??

陳鑫偉發表於2019-07-25

  準備工作:先確保 VS 版本大於 2017,且支援C# 7.0 語言版本。然後新建 .Net Core 專案,在 Nuget 包管理上引入微軟霸霸官方包 System.Runtime.CompilerServices.Unsafe。此包提供了非常底層又符合 .Net CLR 的 API,包括操作指標,引用,記憶體的方法。

  接下來我們就可以利用這個包,去獲取一個字串的記憶體資訊,然後更改這個字串的內容。眾所周知,.Net 中的字串是不可變的,C# 和 .Net 都極大的限制程式設計師不可修改字串的內容,因為一旦修改了字串的內容,將破環 CLR 的規則,使得程式變得不穩定。

  首先我們定義一個與 String 型別欄位結構完全一樣的型別:

public sealed class MyString
{
    /// <summary>
    /// 字串的長度。
    /// </summary>
    public int _stringLength;

    /// <summary>
    /// 字串第一個字元,它與後續的字元的記憶體是連續的。
    /// </summary>
    public char _firstChar;
}

  然後我們定義一個字串:

var str = "Dogwei 牛B!";

  然後我們將這個字串超級轉換為 MyString 型別:

var myStr = System.Runtime.CompilerServices.Unsafe.As<MyString>(str);

  現在我們可以修改字串的內容了:

var str = "Dogwei 牛B!";

var myStr = System.Runtime.CompilerServices.Unsafe.As<MyString>(str);
        
Unsafe.Add(ref myStr._firstChar, str.IndexOf('')) = 'S';

Console.WriteLine(str); // Output : Dogwei SB!

  怎麼樣,是不是很有意思?我們再來試試修改字串長度:

myStr._stringLength = 6;

Console.WriteLine(str); // Output : Dogwei

myStr._stringLength = 999;

Console.WriteLine(str); // Dogwei SB!        ??翽         鄈淭翽...

  長度超過字串本來的長度會輸出一串亂碼。

  同樣轉換之後的方法也是可以執行的:

 

public class Demo
{
    public static void Main()
    {
        var str = "Dogwei 牛B!";

        var myStr = Unsafe.As<MyString>(str);

        myStr.SayHello();
    }
}
public sealed class MyString
{
    /// <summary>
    /// 字串的長度。
    /// </summary>
    public int _stringLength;

    /// <summary>
    /// 字串第一個字元,它與後續的字元的記憶體是連續的。
    /// </summary>
    public char _firstChar;

    public void SayHello()
    {
        var str = Unsafe.As<string>(this);

        var splits = str.Split(' ');

        var name = splits[0];

        var say = splits[1];

        Console.WriteLine($"Hello! my name is {name}, I am {say}.");
    }
}

  但是執行方法有一個必須要注意的地方,就是執行的方法必須是最終方法!(何為最終方法請查閱微軟官方文件 System.Reflection.MethodInfo.IsFinal)。如果不是最終方法會怎麼樣呢?我們來試試:

  同上例,Main 方法保持不變,修改 MyString 為如下:

public class MyString
{
    /// <summary>
    /// 字串的長度。
    /// </summary>
    public int _stringLength;

    /// <summary>
    /// 字串第一個字元,它與後續的字元的記憶體是連續的。
    /// </summary>
    public char _firstChar;

    public virtual void SayHello()
    {
        var str = Unsafe.As<string>(this);

        var splits = str.Split(' ');

        var name = splits[0];

        var say = splits[1];

        Console.WriteLine($"Hello! my name is {name}, I am {say}.");
    }
}

  執行程式後什麼也沒發生,既沒執行,也沒報錯:

 

  到這裡相信大家也對型別強轉超級之術有一點理解,但是這個“巫術”有一些限制:

  1:不能轉換為值型別!

  2:轉換之後必須顯式定義型別,否則將無意義。

 

 

  下一章我們將講超級轉換之術二代!可以轉換任何物件,且是實際意義轉換。

相關文章