2019年9月27日 星期五

C# Generics泛型和C++的泛型



前言這種事情,當然就要把微軟這名號先搬出來先,先看看微軟官方文件裡的文字說明(沒錯,我就是不負責A_____A有問題請找微軟)


「泛型是在 C# 語言和 Common Language Runtime (CLR)  2.0 版中加入的功能。 泛型將型別參數的概念引進 .NET Framework 中,使得類別和方法在設計時,可以先行擱置一個或多個型別規格,直到用戶端程式碼對類別或方法進行宣告或執行個體化時再行處理。」

public class GenericList<T>
{
    void Add(T input) { }
}

class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        GenericList<int> list1 = new GenericList <int>();
        GenericList<string> list2 = new GenericList<string>();
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
    }
}

以下是微軟對泛型的定義和概觀

使用泛用型別以最佳化程式碼重複使用、型別安全性和效能。
泛型的最常見用法是建立集合類別。
.NET Framework 類別庫包含 System.Collections.Generic 命名空間中的數種新泛用集合類別。 在任何可能的情況下都應該使用這些類別,而不是使用類似在 System.Collections 命名空間中的 ArrayList 類別。
可以建立自己的泛用介面、類別、方法、事件和委派 (Delegate)
泛用類別可能會被限制為啟用對特定資料型別上的方法進行存取。
在泛型資料型別中所使用型別的資訊,可以在執行階段透過反映 (Reflection) 取得。


看完了上面這麼長連我也看不懂的內容時,看倌有了解上面的意思嗎?
如果有,那麼恭喜你啦,如果你跟我一樣沒有的,那就來聽聽我的故事。

有一天老闆和我說:「昨天我說的Excel清單你有做了嗎?」我就說有,然後我就把我做好Excel清單給他,他跟我說,不是客戶清單,是要購買的項目清單。


於是我突然靈光一動,就想到了「泛型」(因為清單可以是代表各種類型的清單)(有人會想說:「為什麼我會想到泛型啦XDDD。」)

因為早期在寫一段判斷時,如果在強行別的情況下,常常會因為使用上的限制而讓程式碼寫的很長,很難維護,而且每次要針對不同的型別去各別定義。
不過資訊在進步、程式在爆炸、硬體在飛天的情況下,現在寫程式都有很多新的概念,因為除非你是一個寫低階韌體程式的人,要不然現在記憶體動不動就GG去,CPU動不動就在24核的時代裡,現在在用很古板的程式寫法的話,就很浪費時間。所以泛型的出現,就是為了這個而誕生的。(至少我是這麼認為啦XDDDDDDD如果有高手有不同想法,先別炮我啊啊啊)

如果可以透過泛型的方式來達成,不管是哪一種清單,都會自動的去對應的,那這就是泛型的主要目的。

泛型(Generic Type)它可以讓你在定義ClassMethodInterface時先不用決定型別,到了要實體化的時候再決定其型別,這在集合的應用上更為重要。目的就是要維護「型別」的安全性,讓建構集合(set)時的錯誤能在編譯期間就抓到,不會等執行時才發生錯誤。

所以「泛型」,更白話一點的說法,就是可以泛用通用的類型,簡單來說就是用來增加彈性,且可以明確指定型別是哪一種型別,是不是感覺就猛猛的。

舉例來說,現在的ArrayList就是泛型早期的一種應用

ArrayList list = new ArrayList ();
list.Add(new object());

Add裡塞入的一個object,它可以是任何型別,不過當要對應任何的元素時,要對每一項都進行轉型,那程式碼會變的很冗長。所以就開始讓這個做法定義一個名稱「Generic」,並列入正宮之列。

不過定義都是人給的,每個程式語言的特性都不同,自然對泛型的使用定義上也會有不同,為什麼我會想要拿C++C#來比較呢???

因為我爽XDDDDDDD

其實是因為有很多人會思考C#有部分是參考C++的原型而來C11之後有支援實質的泛型,那它們在語法使用上是不是都很相似呢????



下面是一個C++的例子:

#include <iostream>
using namespace std;
template <typename T>
T add(T aT b)
{
           return a + b;
}
void main()
{
           int num1, num2, sum;
           cin >> num1 >> num2;
           sum = add(num1, num2);
           cout << sum;
}

上面有看到,在C++裡,有一個template,在官方的說明中是這樣的:
template範本是一種建構,在根據使用者所提供的範本參數的引數的編譯時期產生的一般型別或函式。」

template <typename T>
T minimum(const Tlhsconst Trhs)
{
           return lhs < rhs ? lhs : rhs;
}

上述程式碼說明具有單一類型參數的泛型函式樣板T,其傳回值,並呼叫參數 (lhs  rhs) 全都是這個型別。 您可以類似,但依慣例單一大寫的字母您最常使用的任何項目名稱的型別參數。 T是範本參數,而typename關鍵字指出此參數類型的預留位置。 呼叫此函式時,編譯器將會取代每個執行個體T與所指定使用者或編譯器推算出的具象型別引數。 處理程序,編譯器會產生的類別,或從範本函式指樣板具現化;minimum<int>範本的具現化minimum<T>


以上的官方說明(是官方說的喔XDDD)我們可以知道,template也是實作了泛型的原理概念,附上連結可以去看看更完整的內容:

微軟-樣板 (C++)


微軟也許想要跟C++有區別,在使用上微軟官方有說明其差異性:

C++ 樣板和 C# 泛型之間的差異 (C# 程式設計手冊)



從上面來看的話,會了解有怎樣的差異呢??

假設,我想在C中做這樣的事:

class D<T>
{
    class S { }
    D<D<T>.S> ds;
}

C#泛型中執行的話沒問題,在運行時,類型僅為所有引用類型參數構建一次。

但是在C的結構之下呢,當你有D< int>時會發生什麼?內部類型構造D< D< int> .S>類型的字段,因此我們需要構造該類型.但是該類型建構類型為D< D< D< int> .S> .S>等的字段,依此類推為無窮大。

class Program
{
    static void Main(string[] args)
    {
        C.DoIt<string>("Mike");
    }
}

public class C
{
    public static void DoIt<T>(T t)
    {
        ReallyDoIt(t);
    }

    public static void ReallyDoIt(string s)
    {
        System.Console.WriteLine("Natural version: " + s);
    }

    public static void ReallyDoIt<T>(T t)
    {
        System.Console.WriteLine("Generic version: " + t);
    }
}

C++ 編譯器在template時,會根據實際需要的參數型別產生多種版本的類別代碼,也就是說,實際編譯出來的全都是可直接繫結的真實型別。但 C# 的泛型就只是泛型;編譯器並不會因為你在呼叫泛型 method 時分別用到了 string  int 兩種參數而編譯出兩種版本的 method 代碼。

有些人在接觸了這兩種程式語言時,可能會以「泛型(generic)其實就是樣板(template)型別」來理解和解釋泛型,但C# 的泛型和 C++ 的樣板其實骨子裡是不同的。


-雲遊山水為知已逍遙一生而忘齡電腦神手

1 則留言:

  1. 用c#寫的泛型,包成dll;在c++當中,能否使用該dll當中的泛型?

    回覆刪除