【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】
Dart 2:void
的遺產

我在 StackOverflow、Gitter 甚至 Google 內部支援頻道上看到的最常被問到的問題之一是 Dart 2 中以下內建類型之間的差異:Object
、dynamic
、void
和 Null
。
長話短說,Null
(或其他語言中的 Bottom,即「無」)不應該在大部分真實使用者程式碼中使用,我懷疑在不久的將來我們會看到更多文章和 lint 來溫和地阻止使用。
其餘三個類型則不太清楚,因為在 Dart 2 中,任何東西在執行時都可以是 dynamic
、Object
或 void
,僅根據 靜態 類型簽章而有所不同。因此,讓我們看看幾個 何時 應該使用哪種類型簽章的實際範例。
Object
Object 是 Dart 類別階層的根類別,Dart 中的每個其他類別都是 Object
的子類別——包括像 int
、double
或 bool
這樣的「原始」類型。它保證了一些東西:一個 hashCode
屬性,一個 ==
運算子,一個 toString
方法。
實際上,我使用 Object
作為窮人的聯合類型——期望使用者在使用某個東西之前使用 is
運算子來確定它的真實類型。**我不使用 dynamic
**,因為正如下一節所述,它會停用重要的靜態分析,並且更容易讓您進入無效狀態。
1 | Object readProperty(String name) { ... } |
1 | void main() { |
另一個選項是使用 Object
來宣告您 不關心 資料結構的內部類型是什麼,例如 List<Object>
可能表示「任何東西的列表」。例如,在編寫一個組合 List
中每個元素的 hashCode
的函式時,這就派上用場了:
1 | int hashList(List<Object> elements) { ... } |
Object
的一個很好的特性(與 dynamic
相比)是,如果您嘗試在其上調用一個不存在的方法,您將立即獲得分析和編譯器回饋。例如,以下程式碼會產生一個 靜態錯誤:
1 | void main() { |
然而,在實際應用中,Object
是相當(並且有意地)有限的。我希望 Dart 將獲得對方法重載的支援,這將允許我在真實程式碼中顯著減少 Object
類型的使用。
dynamic
我個人在 Dart 2 中 從不 使用 dynamic
類型。在我看來,它是 Object
和一個特殊指令的聯合,該指令告訴工具和編譯器 停用靜態分析檢查。也就是說,以下程式碼是合法的,並且只會在執行時出現錯誤(而不是靜態地!):
1 | void main() { |
在 Dart 1 中,dynamic
無處不在,任何其他靜態類型都是為了 IDE 和靜態分析支援——但編譯器(和執行時)將所有東西都視為 dynamic
。儘管如此,在 Dart 2 中仍然有一些不幸的「陷阱」可能會 意外地 建立一個動態類型的變數:
1 | computeAge() => 5; // 返回類型是 dynamic |
1 | void main() { |
更糟糕的是,dynamic
調用會丟失 Dart 2 中至關重要的類型資訊:
1 | class User { |
1 | void main() { |
發生此錯誤的原因是因為這裡的實際調用是:
1 | users.map((dynamic u) => u.name); |
…它沒有足夠的靜態類型資訊來產生 Iterable<String>
。透過將 users
修正為正確的類型(並避免動態調用),一切正常:
1 | void main() { |
void
最後,我們有 void
,這是 Dart 2 中最新的類型。在 Dart 1 中,void
只能用作函式的返回類型(例如 void main()
),但在 Dart 2 中,它已被 泛化,並且可以在其他地方使用,例如 Future<void>
。
void
類型在語義上類似於 Object
(它可以是任何東西),但有一些額外的限制——void
類型不能用於任何東西(即使是 ==
或 hashCode
),並且將某個東西賦值給 void
類型是無效的:
1 | void foo() {} |
在 實際應用中,我使用 void
來表示「任何東西,我不關心元素」,或者更常見的是表示「省略」,例如在 Future<void>
或 Stream<void>
中:
1 | /// 清除快取。 |
在上面的程式碼片段中,我不希望使用者嘗試使用提供的 Future
的返回值,因為它不相關。我見過使用 Future<Null>
來達到此目的的範例,這實際上是在 Future<void>
成為可能 之前 的一種解決方法。
例如,這在靜態上是正常的,但在執行時在 Dart 2 中是無效的:
1 | import 'dart:async'; |
…而使用 Future<void>
作為 doAThing()
是有效且正確的。
另一個例子可能是不帶任何事件資料的 Stream
:
1 | /// 當使用者登出系統時觸發事件。 |
另一個更實際的用途是實作一個具有您不會使用的泛型類型引數的類別。例如,實作流行的 訪問者模式,當 C(上下文)類型引數未使用時,我們可以透過傳遞 void
來忽略它:
1 | abstract class Visitor<N, C> { |
我希望這篇簡短的文章可以幫助您圍繞使用 Object
、dynamic
、void
做出 API 決策。如果您有任何其他問題或想法,請留言!
Dart 2:void
的遺產 最初發佈於 Medium 上的 Dart,人們在那裡透過突出顯示和回應這個故事來繼續討論。