C# 11 新功能 -- static virtual members in interfaces and generic math

static virtual members in interfaces 與 generic math 這兩個新功能有相關,因為有了前者,才會使後者成為可能,而且是此次改版我個人最喜歡的新變化。

static virtual members in interfaces

這是一個毀三觀的新功能,C# 8 的 default interface implementation 已經算跌破眼鏡的設計了,這次的 static virtual members in interfaces 更是勇不可擋,在介面裡可以設計「虛擬的靜態成員」!?讓我們來看看以下的程式碼:

public interface IMyAreaAddOperator<T>
{
    static abstract int AddArea(T source, T other);
}

這就是在介面中定義一個靜態虛擬成員的方式。當你建立一個類別實作這個介面的時候,就必須要完整實作這個靜態成員的內容,即使是抽象類別也一樣。

public class MyRectangle : IMyAreaAddOperator<MyRectangle>
{
    public int Width { get; set; }
    public int Height { get; set; }

    public int Area { get => Width * Height; }

    public static int AddArea(MyRectangle source, MyRectangle other
    {
        return source.Area + other.Area;
    }
}
這個變化對現有型別有甚麼影響?

在 .NET 7 裡面有非常多既有的型別和這件事情有關,只要原來有「加 +」「減 -」「乘 *」「除 /」「累加 ++」「累減 --」「餘數 %」「邏輯 & | ^ ~」「比較 > < ≥ ≤」這些運算子,以及具有 Parse/ TryPares 靜態方法的型別應該都因為這新功能而加上了一大堆的新介面。

例如在 .NET 6 的時候,我們最常用的 System.Int32 只有實作以下介面:

  • IComparable
  • IComparable<Int32>
  • IConvertible
  • IEquatable<Int32>
  • IFormattable
  • ISpanFormattable

到了 .NET 7 可真是不得了了,一個 System.Int32 具有超過 20 個新介面,多到我實在是不想列出來了,諸位看倌可以到 Microsoft Learn 文件查看。而這個新功能造就了另外一個功能 – generic math。

generic math

這中文翻譯叫做「泛型數學」,切合題意卻又說不出來的奇妙。這在解決一個很常見的問題:『有多個不同型別使用相同數學運算方式』,這在以前最常見的做法就是多載,比方說 Linq 裡的 Sum,但這種作法有一點討人厭的地方就是每個方法的差異都是參數型別,程式碼的邏輯內容根本就一樣。

我曾經在寫一個範例的時候就遇到這個困擾,幾個不同參數的多載方法內部重複了一大坨的程式碼,就為了型別不一樣,而且不想重複過多的程式碼就弄了一個不甚理想的解決方式:

private Func<T, T, T> Subtract<T>() where T : struct
{           
    var parameterX = Expression.Parameter(typeof(T), "x");
    var parameterY = Expression.Parameter(typeof(T), "y");
    var body = Expression.Subtract(parameterX, parameterY);
    var subtract = Expression.Lambda<Func<T, T, T>>(body, parameterX, parameterY).Compile();
    return subtract;
}

可以解決問題,但是強固性不足,誰知道那個傳進來的型別有沒有實作減法的運算子呢?但在那個階段,大概也只能這麼硬幹一下。當時我就想:「為什麼泛型限制不搞個可以限制運算子的呢?」,這下 C# 11 可解決我這個困擾了,可以藉由約束宣告某個運算子的介面來達成。比方我們剛剛講的 Sum,可以寫成以下的方式,就不需要一大堆多載來礙眼了。

 internal class Program
 {
     static void Main(string[] args)
     {
         var intList = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
         Console.WriteLine(intList.GenericSum());
         var doubleList = new List<double> { 0.1, 0.2, 0.3 };
         Console.WriteLine(doubleList.GenericSum());  
     }
 }

 public static class GenericMathExtensions
 {
     public static T GenericSum<T>(this IEnumerable<T> source) where T : struct, IAdditionOperators<T,T,T>
     {
         T result = default(T);
         foreach (var item in source)
         {
             result += item;
         }
         return result;
     }
 }

程式碼看起來清爽多了,同時保持強固性,我覺得這是 C# 11 新功能裡面最棒的。範例可以參考 範例1範例2