UnsafeAccessorAttribute 指南 (2)

UnsafeAccessor 於 .NET 9 後有一些改善,本篇說明這些在 .NET 8 未竟全功的修正。

泛型支援

UnsafeAccessor 在 .NET 8 時代並未考慮對於泛型型別的支援,但是對於已封閉的泛型型別會出現預期外的成功。.NET 9 則新增了對泛型型別的支援,並且修正了預期外成功的問題。

例如在 .NET 8 撰寫以下的存取器。

  public static class GenericAccessor
  {
      /// <summary>
      /// Accessing public constructor of generic class List<int> (success)
      /// </summary>
      /// <returns></returns>
      [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
      public extern static List<int> Create();

      /// <summary>
      /// Accessing public method Add of generic class List<int> (failure)
      /// </summary>
      /// <param name="list"></param>
      /// <param name="item"></param>
      [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Add")]
      public extern static void Add(List<int> list, int item);
  }

當呼叫 GenericAccessor.Create 時候是會執行成功 (註1) ,可是呼叫 GenericAccessor.Add 會拋出 「System.MissingMethodException: Method not found: System.Collections.Generic.List`1.Add.」

 internal class Program
 {
     static void Main(string[] args)
     {
         // create List<int> instance via UnsafeAccessor (.NET8 success)
         // if you change TargetFramework to net9.0, it will thorw exception -- System.InvalidProgramException: Generic type constraints do not match.
         var list = GenericAccessor.Create();
         Console.WriteLine($"list count {list.Count}");

         // this will throw exception (.NET 8) -- System.MissingMethodException: Method not found: System.Collections.Generic.List`1.Add.            
         GenericAccessor.Add(list, 999);
         Console.WriteLine($"list count {list.Count}, index 0 is {list[0]}");
     }
 }

註1 : 這點在 .NET 9.0 已經修正,會拋出 「System.InvalidProgramException: Generic type constraints do not match.」

.NET 9 新增對泛型Accessor 類別的支援。

 public static class GenericAccessor<T>
 {
     /// <summary>
     /// Accessing public constructor of generic class List<T>
     /// </summary>
     /// <returns></returns>
     [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
     public extern static List<T> Create();
     /// <summary>
     /// Accessing public method Add of generic class List<T>
     /// </summary>
     /// <param name="list"></param>
     /// <param name="item"></param>
     [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Add")]
     public extern static void Add(List<T> list, T item);
 }

呼叫方式也很自然。

 static void Main(string[] args)
 {
     var list = GenericAccessor<int>.Create();
     Console.WriteLine($"list count {list.Count}");
                
     GenericAccessor<int>.Add(list, 999);
     Console.WriteLine($"list count {list.Count}, index 0 is {list[0]}");
 }
UnsafeAccessorTypeAttribute

.NET 10 新增了 UnsafeAccessorTypeAttribute,這個 attribute 可以解決兩個問題

  1. 因為安全因素型別封裝層級低於 public 以至於無法存取
  2. 靜態型別
解決封裝層級問題

第一個例子說明解決封裝層級的問題,假設有個私有巢狀型別 InnerClass 位於容器 OuterClass 之中。

  public class OuterClass
  {
      private object _innerInstance = new InnerClass();
      /// <summary>
      /// this is an nested inner private class
      /// </summary>
      private class InnerClass
      {
          private void ShowMessage()
          {
              Console.WriteLine("Hello from InnerClass!");
          }
      }
  }

則我們可以如此撰寫存取器,利用 UnsafeAccessorTypeAttribute 指定型別為 InnerClass (參考 CallShowMessage 方法的參數)。

 public static class OuterClassAccessor
 {
     /// <summary>
     /// Accessing private field _innerInstance of OuterClass
     /// </summary>
     /// <param name="outerInstance"></param>
     /// <returns></returns>
     [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_innerInstance")]
     public extern static ref object GetInnerInstance(OuterClass outerInstance);

     /// <summary>
     /// Accessing private nested class InnerClass's method ShowMessage 
     /// </summary>
     /// <param name="innerInstance"></param>
     [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ShowMessage")]
     public extern static void CallShowMessage([UnsafeAccessorType("UnsafeAccessorSample006.OuterClass+InnerClass")] object instance);
 }

呼叫方式也很簡單。

  static void Main(string[] args)
  {
      ref var innerInstance = ref OuterClassAccessor.GetInnerInstance(new OuterClass());
      OuterClassAccessor.CallShowMessage(innerInstance);
  }
解決靜態類別問題

我們就直接拿 System.Math class 來當作存取靜態類別成員的範例,雖然它的成員是 public ,根本也沒必要這麼搞,但主要是要展示 Accessor 的部分,就不需計較這些了。

  internal class Program
  {
      static void Main(string[] args)
      {
          var sqrt = MathAccessor.Sqrt(null, 16.0);
          Console.WriteLine($"The square root of 16.0 is {sqrt}");
          var pow = MathAccessor.Pow(null, 2.0, 8.0);
          Console.WriteLine($"2.0 to the power of 8.0 is {pow}");
      }
  }

  public static class MathAccessor
  {
      /// <summary>
      /// Accessing static method Sqrt of Math class
      /// </summary>
      /// <param name="value"></param>
      /// <returns></returns>
      [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Sqrt")]
      public extern static double Sqrt([UnsafeAccessorType("System.Math")] object _, double value);


      /// <summary>
      /// Accessing static method Pow of Math class
      /// </summary>
      /// <param name="_"></param>
      /// <param name="x"></param>
      /// <param name="y"></param>
      /// <returns></returns>
      [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Pow")]
      public extern static double Pow([UnsafeAccessorType("System.Math")] object _, double x, double y);
  }

由於 System.Math 是個靜態類別,所以是無法當成參數型別宣告的,這時就可以使用 UnsafeAccessorTypeAttribute 指定型別為 System.Math 繞過這個問題。

本篇參考範例在此 (從 UnsafeAccessorSample004 開始)。