準備工作:先確保 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:轉換之後必須顯式定義型別,否則將無意義。
下一章我們將講超級轉換之術二代!可以轉換任何物件,且是實際意義轉換。