C#12 新功能 (3)

這篇來談兩個新功能,ref readonly parameters 與 alias any type

ref readonly parameters

在 C# 7.2 版對於方法參數的修飾詞多出一個 in 修飾詞,這個修飾詞表明這是一個 by reference 傳遞的參數,並且在方法內部無法重新指派新的值給這個參數。

C#12 居然又出現了一個 ref readonly 修飾詞,作用也差不多,所以表明這個修飾詞一定有某些作用是原來的 in 修飾詞無法做到的,來看看怎麼回事。

首先,這個修飾詞用法很簡單,如下:

 public static int Do(ref readonly int x)
 {
     return x + 1;
 }

在呼叫端可以使用 ref 或 in 修飾詞,以下兩種呼叫皆合法:

 Do(ref x);
 Do(in x);

這就是和 in 修飾詞在作用上的不同,如果一個方法的參數使用了 in 修飾詞,那呼叫端也得使用 in 修飾詞;但方法參數若使用 ref readonly 修飾詞,呼叫端可以使用 ref 或 in。

就個人觀點來看,這個微妙的差別在於「方法修改後保持呼叫者的使用介面毋須更動」。那我們在公堂之上假設一下,假設我們有一個自訂的函式庫撰寫在 C# 6,這個方法參數被設計為 pass by reference,而且希望方法內部不能更動此參數值,我們可能怎麼做?

 public static int Do(ref int x, int y)
 {
     // 請勿在此更動 x 的值
     return x + y;
 }

使用 ref 是必要的,但如何防止其他開發者在方法內容中重新指派 x ?大致上就是加上一行註解當警語。

到了 C# 7.2 ,我們可能會想要把它改成 in,於是變成這樣:

 public static int Do(in int x, int y)
 {          
     return x + y;
 }

已經不用加上警語了,那個 in 就代表了一切,而且編譯器不允許你在方法內部重新指派 x。完美嗎?如果這個方法沒有人用過,這件事就完美了。但如果這個函式屬於某個獨立的 DLL,而這 DLL 已經有一大堆人用,再加上有一堆人呼叫了這個方法;當你很開心宣布函式庫已經更新到最新版本,請大夥兒踴躍升級,那就是你電話接到手軟 (而且大部分是客訴) 的日子,因為每個人都會需要為了這個升級把所有呼叫端的 ref 改成 in。

ref readonly 就是在解決這個問題,若我們把 ref 替換成 ref readonly,呼叫端保持 ref 修飾詞還是可以的,這樣就沒有人會覺得這個修改會造成困擾了。

alias any type

其實早期就有 alias type 的功能,比方說:

using Scores = System.Collections.Generic.List<int>;
using MyPoint = System.ValueTuple<int, int>;

namespace ConsoleApp10
{
    class Program
    {
        static void Main(string[] args)
        {
            Scores s = new Scores { 1, 2, 3, 4, 5 };
            foreach(var item in s)
            {
                Console.WriteLine(item);
            }
            MyPoint p = (5, 10);
            Console.WriteLine(p);
            Console.ReadLine();
        }
    }
}

但有個小小的缺點,非命名類型不可用,像是 ValueTuple 語法糖或是陣列,因此在過去無法這麼寫:

using MyPoint = (int x, int y); 
using Numbers = int[];

現在 C#12 這麼寫就沒問題了,陣列的部分順手用上 collection expressions。

using MyPoint = (int x, int y); 
using Numbers = int[];
namespace AliasAnyTypeSample001
{
    internal class Program
    {
        static void Main(string[] args)
        {
            MyPoint p = new MyPoint(10, 90);           
            Numbers numbers = [1,2,3,4,5,6,7,8,9,10];
        }
    }
}

alias type 的功能可以讓型別具有一個有意義的別名,算是個不錯的功能。