狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

C#.NET Core 泛型(Generic)的好處和底層原理

freeflydom
2025年1月8日 11:11 本文熱度 91

簡介

泛型參考資料爛大街,基本資料不再贅述,比如泛型接口/委托/方法的使用,逆變與協變。

泛型好處有如下幾點

  1. 代碼重用
    算法重用,只需要預先定義好算法,排序,搜索,交換,比較等。任何類型都可以用同一套邏輯
  2. 類型安全
    編譯器保證不會將int傳給string
  3. 簡單清晰
    減少了類型轉換代碼
  4. 性能更強
    減少裝箱/拆箱,泛型算法更優異。

為什么說泛型性能更強?

主要在于裝箱帶來的托管堆分配問題以及性能損失

  1. 值類型裝箱會額外占用內存
            var a = new List<int>()
            {
                1,2, 3, 4
            };
            var b = new ArrayList()
            {
                1,2,3,4
            };

變量a:72kb

變量b:184kb

  1. 裝箱/拆箱會消耗額外的CPU
	public void ArrayTest()
	{
		Stopwatch stopwatch = Stopwatch.StartNew();
		stopwatch.Start();
		ArrayList arrayList = new ArrayList();
		for (int i = 0; i < 10000000; i++)
		{
			arrayList.Add(i);
			_ = (int)arrayList[i];
		}
		stopwatch.Stop();
		Console.WriteLine($"array time is {stopwatch.ElapsedMilliseconds}");
	}
	public void ListTest()
	{
		Stopwatch stopwatch = Stopwatch.StartNew();
		stopwatch.Start();
		List<int> list = new List<int>();
		for (int i = 0; i < 10000000; i++)
		{
			list.Add(i);
			_ = list[i];
		}
		stopwatch.Stop();
		Console.WriteLine($"list time is {stopwatch.ElapsedMilliseconds}");
	}

如此巨大的差異,無疑會造成GC的管理成本增加以及額外的CPU消耗。

思考一個問題,如果是引用類型的實參。差距還會如此之大嗎?
如果差距不大,那我們使用泛型的理由又是什么呢?

開放/封閉類型

CLR中有多種類型對象 ,比如引用類型,值類型,接口類型和委托類型,以及泛型類型。

根據創建行為,他們又被分為開放類型/封閉類型

為什么要說到這個? 泛型的一個有優點就是代碼復用,只要定義好算法。剩下的只要往里填就好了。比如List<>開放給任意實參,大家都可以復用同一套算法。

舉個例子

  1. 開放類型是指類型參數尚未被指定,他們不能被實例化 List<>,Dictionary<,>,interface 。它們只是搭建好了基礎框架,開放不同的實參
            Type it = typeof(ITest);
            Activator.CreateInstance(it);//創建失敗
            Type di = typeof(Dictionary<,>);
            Activator.CreateInstance(di);//創建失敗
  1. 封閉類型是指類型已經被指定,是可以被實例化 List<string>,String 就是封閉類型。它們只接受特定含義的實參
            Type li = typeof(List<string>);
            Activator.CreateInstance(li);//創建成功

代碼爆炸

所以當我們使用開放類型時,都會面臨一個問題。在JIT編譯階段,CLR會獲取泛型的IL,再尋找對應的實參替換,生成合適的本機代碼。
但這么做有一個缺點,要為每一種不同的泛型類型/方法組合生成,各種各種的本機代碼。這將明顯增加程序的Assembly,從而損害性能
CLR為了緩解該現象,做了一個特殊的優化:共享方法體

  1. 相同類型實參,共用一套方法
    如果一個Assembly中使用了List<Struct>另外一個Assembly也使用了List<Struct>
    那么CLR只會生成一套本機代碼。

  2. 引用類型實參,共用一套方法
    List<String>與List<Stream> 實參都是引用類型,它們的值都是托管堆上的指針引用。因此CLR對指針都可以用同一套方式來操作
    值類型就不行了,比如int與long. 一個占用4字節,一個占用8字節。占用的內存不長不一樣,導致無法用同一套邏輯來復用

眼見為實1

示例代碼
    internal class Program
    {
        static void Main(string[] args)
        {
            var a = new Test<string>();
            var b = new Test<Stream>();
            
            Debugger.Break();
        }
    }
    public class Test<T>
    {
        public void Add(T value)
        {
		
        }
        public void Remove(T value)
        {
        }
    }

變量a:

變量b

仔細觀察發現,它們的EEClass完全一致,它們的Add/Remove方法的MethodDesc也完全一直。這印證了上面的說法,引用類型實參引用同一套方法。

眼見為實2

點擊查看代碼
    internal class Program
    {
        static void Main(string[] args)
        {
            var a = new Test<int>();
            var b = new Test<long>();
            var c = new Test<MyStruct>();
            
            Debugger.Break();
        }
    }
    public class Test<T>
    {
        public void Add(T value)
        {
        }
        public void Remove(T value)
        {
        }
    }
    public struct MyStruct
    {
        public int Age;
    }

我們再把引用類型換為值類型,再看看它們的方法表。
變量a:

變量b:

變量c:

一眼就能看出,它們的MethodDesc完全不一樣。這說明在Assembly中。CLR為泛型生成了3套方法。

細心的朋友可能會發現,引用類型的實參變成了一個叫System.__Canon的類型。CLR 內部使用 System.__Canon 來給所有的引用類型做“占位符”使用
有興趣的小伙伴可以參考它的源碼:coreclr\System.Private.CoreLib\src\System__Canon.cs

為什么值類型無法共用同一套方法?

其實很好理解,引用類型的指針長度是固定的(32位4byte,64位8byte),而值類型的長度不一樣。導致值類型生成的底層匯編無法統一處理。因此值類型無法復用同一套方法。

眼見為實

?點擊查看代碼
    internal class Program
    {
        static void Main(string[] args)
        {
            var a = new Test<int>();
            a.Add(1);
            var b = new Test<long>();
            b.Add(1);
            var c = new Test<string>();
            c.Add("");
            var d = new Test<Stream>();
            d.Add(null);
            
            Debugger.Break();
        }
    }
    public class Test<T>
    {
        public void Add(T value)
        {
            var s = value;
        }
        public void Remove(T value)
        {
        }
    }
//變量a
00007FFBAF7B7435  mov         eax,dword ptr [rbp+58h]  
00007FFBAF7B7438  mov         dword ptr [rbp+2Ch],eax    //int 類型步長4 2ch
//變量b
00007FFBAF7B7FD7  mov         rax,qword ptr [rbp+58h]  
00007FFBAF7B7FDB  mov         qword ptr [rbp+28h],rax  //long 類型步長8 28h 匯編不一致
//變量c
00007FFBAF7B8087  mov         rax,qword ptr [rbp+58h]  
00007FFBAF7B808B  mov         qword ptr [rbp+28h],rax  // 28h
//變量d
00007FFBAF7B8087  mov         rax,qword ptr [rbp+58h]  
00007FFBAF7B808B  mov         qword ptr [rbp+28h],rax  // 28h 引用類型地址步長一致,匯編也一致。

泛型的數學計算

在.NET 7之前,如果我們要利用泛型進行數學運算。是無法實現的。只能通過dynamic來曲線救國

.NET 7中,引入了新的數學相關泛型接口,并提供了接口的默認實現。

https://learn.microsoft.com/zh-cn/dotnet/standard/generics/math

數學計算接口的底層實現

C#層:
相加的操作主要靠IAdditionOperators接口。

IL層:
+操作符被JIT編譯成了op_Addition抽象方法

對于int來說,會調用int的實現
System.Int32.System.Numerics.IAdditionOperators

對于long來說,會調用long的實現
System.Int64.System.Numerics.IAdditionOperators

從原理上來說很簡單,BCL實現了基本值類型的所有+-*/操作,只要在泛型中做好約束,JIT會自動調用相應的實現。

結論

泛型,用就完事了。就是要稍微注意(硬盤比程序員便宜多了)值類型泛型造成的代碼爆炸。

轉自https://www.cnblogs.com/lmy5215006/p/18529501


該文章在 2025/1/8 11:11:15 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved