c 判斷 泛型t 型別 C 泛型方法的型別推斷

2021-10-13 07:02:23 字數 3498 閱讀 4399

這裡所謂的「泛型方法的型別推斷」,指的是根據已有的方法實參的型別,推斷出泛型方法的型別實參。例如乙個泛型方法 void method(t args),如果我給出方法實參型別是 int,那麼希望能夠推斷出 t = int。

或者舉個複雜點的例子,對於下面的泛型方法定義:

void method(ilist a, params t args);

再給出引數型別為:

我希望能夠正確的推斷出t的型別分別為int、int和int。

後來參考了《csharp language specification》v5.0 中 7.5.2 型別推斷一節,規範中給出了 c# 中進行型別推斷的兩階段演算法,演算法分為兩階段主要是為了支援實參表示式和匿名函式的推斷,而我的需求則要簡單很多,只要支援普通的引數就可以了。又參考了 7.5.2.13 方法組轉換的型別推斷一節,最終得到了下面的簡化演算法。

首先對幾個名詞進行區分:型別形參、型別實參、方法形參和方法實參。

對於泛型方法定義 void method(t a),其中的 t 是型別形參,t a 是方法形參。

對於相應的封閉泛型方法的呼叫 method(10),其中的 int 就是型別實參,10 就是方法實參。

泛型方法的型別推斷,從形式上來定義,就是對給定泛型方法 tr m(t1 x1, …, t_m x_m),其中 tr 是返回值,x1, …, xn 是型別形參,t1, …, t_m 是方法形參,和乙個委託型別 d(u1 x1, …, u_m x_m),找到一組型別實參 s1, …, sn,使表示式 m 與 d 相容(d 可由m 隱式轉換而來)。

該演算法首先認為所有 xi 均未固定(即沒有預設值),並從 d 的每個實參型別 ui 到 m 的對應形參型別 ti 進行下限推斷(前提是 ti 包含型別形參,即containsgenericparameters == true),但是如果 xi 為 ref 或 out 形參,則從 ui 到 ti 進行精確推斷。如果沒有為任何 xi 找到界限,則型別推斷將失敗。否則,所有將 xi 均固定到對應的 si,它們是型別推斷的結果。下面給出詳細的推斷演算法,這裡的演算法經過了我的修改,與原規範並不完全相同。

一、精確推斷

這裡的精確推斷指的是對於給定的實參型別 u,找到合適的形參型別 v,使得 u == v。

按如下所述從型別 u 到型別 v 進行精確推斷:

如果 v 是 xi 之一,則將 u 新增到 xi 的精確界限集中。

否則,通過檢查是否存在以下任何一種情況來確定集合 v1, …, v_k 和 u1, …, u_k:

v 是陣列型別 v1[…],u 是具有相同秩的陣列型別 u1[…]。

v 是型別 v1?,u 是型別 u1?。

v 是構造型別 c 並且 u 是構造型別 c。

如果存在以上任意情況,則從每個 ui 到對應的 vi 進行精確推斷。

否則,型別推斷將失敗。

二、下限推斷

這裡的下限推斷指的是對於給定的實參型別 u,找到合適的形參型別 v,使得 v.isimplicitfrom(u)。

按如下所述從型別 u 到型別 v 進行下限推斷:

如果 v 是 xi 之一,則將 u 新增到 xi 的下限界限集中。

否則,如果 v 為 v1? 型別,而 u 為 u1? 型別,則從 u1 到 v1 進行下限推斷。

否則,如果 v 是陣列型別 v1[…],u 是具有相同秩的陣列型別 u1[…],或者 v 是乙個 ienumerable、icollection 或 ilist,u 是一維陣列型別 u1,如果不知道 u1 是引用型別,則從 u1 到 v1 進行精確推斷,否則進行下限推斷。

否則,如果 v 是構造類、結構、介面或委託型別 c,並且存在唯一型別 c,使 u 等於、(直接或間接)繼承自或者(直接或間接)實現 c(「唯一性」限制表示對於 inte***ce c{} class u: c, c{},不進行從 u 到 c 的推斷,因為 u1 可以是 x 或y。),則從每個 ui 到對應的 vi 進行推斷,如果不知道 u1 是引用型別,則進行精確推斷,否則推斷依賴於 c 的第 i 個型別引數:

如果該引數是協變的,則進行下限推斷。

如果該引數是逆變的,則進行上限推斷。

如果該引數是固定的,則進行精確推斷。

否則,型別推斷將失敗。

三、上限推斷

這裡的上限推斷指的是對於給定的實參型別 u,找到合適的形參型別 v,使得 u.isimplicitfrom(v)。

按如下所述從型別 u 到型別 v 進行上限推斷:

如果 v 是 xi 之一,則將 u 新增到 xi 的上限界限集中。

否則,如果 v 為 v1? 型別,而 u 為 u1? 型別,則從 u1 到 v1 進行上限推斷。

否則,如果 v 是陣列型別 v1[…],u 是具有相同秩的陣列型別 u1[…],或者 v 是一維陣列型別 v1,u 是乙個 ienumerable、icollection 或ilist,如果不知道 u1 是引用型別,則從 u1 到 v1 進行精確推斷,否則進行上限推斷。

否則,如果 u 是構造類、結構、介面或委託型別 c,v 是等於、(直接或間接)繼承自或者(直接或間接)實現唯一型別 c的類、結構、介面或委託型別(「唯一性」限制表示如果我們有 inte***ce c{} class v: c>, c>{},則不進行從 c 到 v的推斷。也不進行從 u1 到 x或 y的推斷。),則從每個 ui 到對應的 vi 進行推斷,如果不知道 u1 是引用型別,則進行精確推斷,否則推斷依賴於 c 的第 i 個型別引數:

如果該引數是協變的,則進行上限推斷。

如果該引數是逆變的,則進行下限推斷。

如果該引數是固定的,則進行精確推斷。

否則,型別推斷將失敗。

四、固定

固定是為了根據之前的演算法得到的界限集,推斷出型別引數的合適的值。

具有界限集的型別變數 xi 按如下方式固定:

候選型別集 ui 是在 xi 的界限集中的所有型別的集合。

然後我們依次檢查 xi 的每個界限:對於 xi 的每個精確界限 u,將與 u 不同的所有型別 ui 都從候選集中移除(要求 u == ui)。對於 xi 的每個下限u,將不存在從 u 進行的隱式轉換的所有型別 ui 都從候選集中移除(要求 ui.isimplicitfrom(u))。對於 xi 的每個上限 u,將不存在從其到 u 進行的隱式轉換的所有型別 ui 都從候選集中移除(要求 u.isimplicitfrom(ui))。

如果在剩下的候選型別 ui 中,存在唯一型別 v,該型別可由其他所有候選型別經隱式轉換而來,則將 xi 固定到 v(也就是說,要求 v 是其中最通用的型別)。

否則,型別推斷將失敗。

以上就是泛型方法的型別推斷演算法,其中只考慮了方法實參和方法形參一一對應的情況,如果需要處理 params t 引數,則需要對最後乙個引數進行特殊處理,並分別使用 t 和 t 進行一次型別推斷。做兩次型別推斷,就是為了判斷是否是方法的展開形式的呼叫。

或者說,對於泛型方法定義

void method(t a, params t args);

如果引數為和,雖然t對應的實參是相同的,但推斷出的t卻是不同的,這就需要利用兩次型別推斷來處理。

這個演算法的實現加上注釋大概有 500 多行,這裡就不再貼出,基本就是按照上面的 4 步來的,只是在一些細節上採用了更高效的做法。所有原始碼可以見這裡。

c 判斷 泛型t 型別 C 基礎篇 泛型

在開發程式設計中,我們經常會遇到功能非常相似的功能模組,只是他們的處理的資料不一樣,所以我們會分別採用多個方法來處理不同的資料型別。但是這個時候,我們就會想乙個問題,有沒有辦法實現利用同乙個方法來傳遞不同種型別的引數呢?這個時候,泛型也就因運而生,專門來解決這個問題的。泛型是在c 2.0就推出的乙個...

C 泛型型別 泛型方法

泛型會宣告型別引數 泛型的消費者需要提供型別引數來把佔位符型別填充 public class stack var stack newstack int stack.push 2 stack.push 3 int x stack.pop 2int y stack.pop 3stack open typ...

泛型 泛型類 泛型方法 泛型擦除

1 是什麼?一種允許我們在不確定引數型別時候使用的型別。例如我不知道a方法應該會傳string還是int,我就用個泛型先佔坑。2 為什麼要用泛型?泛型可以在編譯期自動確定具體型別,檢查型別是否匹配,可以提高 的重用率,減少冗餘編碼。3 泛型與object的區別?像上面說的我不知道方法a的引數型別,其...