著者 | Nate Hill
翻訳者 | 弯月
提供 | CSDN(ID:CSDNnews)
TypeScript は非常に優れています。強い型付けと迅速な開発を完璧に組み合わせているため、非常に使いやすく、多くの状況でデフォルトでこのライブラリを選びます。しかし、完璧な言語は存在せず、TypeScript が最適なツールではない場合もあります:
- パフォーマンスが重要な場合(例:リアルタイム通信、ビデオゲーム)
- ネイティブコード(C/C++ や Rust など)との連携が必要な場合
- より厳密な型システムが必要な場合(例:金融システム)
このような場合、TypeScript 開発者は他の言語を選択するのが良いでしょう。C#、Go、Java は非常に良い選択肢です。これらは TypeScript よりもはるかに高速で、それぞれに長所があります。C#は TypeScript と非常に相性が良いです。その理由を説明します。

1. TypeScript は C#を追加した JavaScript
C#が TypeScript と非常に相性が良いのは、両者がまるで同じ言語のように見えるからです。両方とも Anders Hejlsberg によって設計されており、多くの点で TypeScript は C#を追加した JavaScript です。それらの機能や構文は非常に似ているため、同じプロジェクトで両方を組み合わせて使用するのは非常に簡単です。さらに重要なのは、C#の言語が TypeScript と非常に似ているため、開発者がコードを読んだり書いたりするのも非常に楽です。
逆に、Go はまったく異なる言語です:クラスなし、継承なし、例外なし、パッケージレベルのカプセル化なし(クラスレベルのみ)、構文も完全に異なります。もちろんこれは必ずしも悪いことではありませんが、開発者はコードを異なる方法で設計し直す必要があるため、Go と TypeScript を同時に使用するのは難しいです。ただし、Java は C#と非常に似ていますが、C#と TypeScript の両方が持つ多くの機能がまだ不足しています。
2. C#と TypeScript の類似点
おそらくご存知の通り、C#と TypeScript には多くの類似点があります。例えば、C ベースの構文、クラス、インターフェース、ジェネリックスなどです。以下に、両者の類似点を詳しく列挙します:
- 2.1 async/await
- 2.2 ラムダ式と関数型配列メソッド
- 2.3 null を扱うための演算子(?、!、??)
- 2.4 分割代入
- 2.5 コマンドラインインターフェース(CLI)
- 2.6 基本機能(クラス、ジェネリックス、エラー、列挙型)
2.1 async/await
まず、C#と JavaScript はどちらも async/await を使用して非同期コードを処理します。JavaScript では非同期操作は Promise で表され、アプリケーションは非同期操作の終了を await できます。C#の Promise は実際には Task であり、概念的には Promise と完全に同じで、対応するメソッドもあります。以下の例は、両方の言語での async/await の使用法を示しています。
TypeScript での async/await の例:
async function fetchAndWriteToFile(url: string, filePath:string): Promise<string> {
// fetch() returns aPromise
const response = awaitfetch(url);
const text = awaitresponse.text();
// By the way, we'reusing Deno (https://deno.land)
awaitDeno.writeTextFile(filePath, text);
return text;
}
C#での async/await の例:
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
async Task<string> FetchAndWriteToFile(string url, stringfilePath) {
// HttpClient.GetAsync()returns a Task
var response = await newHttpClient().GetAsync(url);
var text = awaitresponse.Content.ReadAsStringAsync();
awaitFile.WriteAllTextAsync(filePath, text);
return text;
}
以下は、JavaScript の Promise API と同等の C# Task API です:
| JavaScript API | 同等の C# API |
|---|---|
| Promise.all() | Task.WaitAll() |
| Promise.resolve() | Task.FromResult() |
| Promise.reject() | Task.FromException() |
| Promise.prototype.then() | Task.ContinueWith() |
| new Promise() | new TaskCompletionSource() |
2.2 ラムダ式と関数型配列メソッド
C# と JavaScript はどちらもおなじみの => 構文(すなわち アロー関数)を使用して ラムダ式 を表します。以下は TypeScript と C# の比較です。
TypeScript でのラムダ式の使用:
const months = ['January', 'February', 'March', 'April'];
const shortMonthNames = months.filter(month => month.length< 6);
const monthAbbreviations = months.map(month =>month.substr(0, 3));
const monthStartingWithF = months.find(month => {
returnmonth.startsWith('F');
});
C#でのラムダ式の使用:
using System.Collections.Generic;
using System.Linq;
var months = new List<string> {"January","February", "March", "April"};
var shortMonthNames = months.Where(month => month.Length <6);
var monthAbbreviations = months.Select(month =>month.Substring(0, 3));
var monthStartingWithF = months.Find(month => {
returnmonth.StartsWith("F");
});
上記の例は、C# の System.Linq 名前空間にあるいくつかのメソッドを示しており、これらは JavaScript の 関数型配列メソッド に相当します。以下は、JavaScript の 配列メソッド と同等の C# Linq メソッドです。
| JavaScript API | 同等の C# API |
|---|---|
| Array.prototype.filter() | Enumerable.Where() |
| Array.prototype.map() | Enumerable.Select() |
| Array.prototype.reduce() | Enumerable.Aggregate() |
| Array.prototype.every() | Enumerable.All() |
| Array.prototype.find() | List.Find() |
| Array.prototype.findIndex() | List.FindIndex() |
2.3 null 処理演算子
C#と TypeScript の null 処理の特性も同じです:
| 機能名 | 構文 | ドキュメントリンク |
|---|---|---|
| オプショナルプロパティ | property? | TS :https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties、 C#:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types |
| Non-null アサーション | object!.property | TS:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator、 C#:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving |
| オプショナルチェイニング | object?.property | JS :https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining、 C#:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and- |
| Null合体演算子 | object ?? alternativeValue | JS:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator、 C#:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator |
2.4 分割代入
C#はデフォルトで配列やクラスの分割代入をサポートしていませんが、タプルやレコードの分割代入はサポートしており、ユーザー定義型に対して分割代入を定義することもできます。以下は TypeScript と C#での分割代入の例です。
TypeScript での分割代入の例:
const author = { firstName: 'Kurt', lastName: 'Vonnegut' };
// Destructuring an object:
const { firstName, lastName } = author;
const cityAndCountry = ['Indianapolis', 'United States'];
// Destructuring an array:
const [city, country] = cityAndCountry;
C#での分割代入の例:
using System;
var author = new Author("Kurt", "Vonnegut");
// Deconstructing a record:
var (firstName, lastName) = author;
var cityAndCountry = Tuple.Create("Indianapolis","United States");
// Deconstructing a tuple:
var (city, country) = cityAndCountry;
// Define the Author record used above
record Author(string FirstName, string LastName);
2.5 コマンドラインインターフェース(CLI)
私の開発スタイルは、テキストエディタでコードを書き、ターミナルでコマンドを実行してビルド・実行するというものです。TypeScript の場合、node または deno のコマンドラインインターフェース(CLI) を使用する必要があります。C# にも dotnet という名前の 同様の CLI があります(C#の .NET ランタイムに由来します)。以下は dotnet CLI を使ったいくつかの例です。
mkdir app && cd app
# Create a new console application
# List of available app templates:https://docs.microsoft.com/dotnet/core/tools/dotnet-new
dotnet new console
# Run the app
dotnet run
# Run tests (don't feel bad if you haven't written those)
dotnet test
# Build the app as a self-contained
# single file application for Linux.
dotnet publish -c Release -r linux-x64
2.6 基本機能(クラス、ジェネリックス、エラー、列挙型)
これらは TypeScript と C#の間でより基本的な類似点です。以下の例は、これらの側面についての紹介です。
TypeScript クラスの例:
import { v4 as uuidv4 } from'https://deno.land/std/uuid/mod.ts';
enum AccountType {
Trial,
Basic,
Pro
}
interface Account {
id: string;
type: AccountType;
name: string;
}
interface Database<T> {
insert(item: T):Promise;
get(id: string):Promise<T>;
}
class AccountManager {
constructor(database:Database<Account>) {
this._database =database;
}
asynccreateAccount(type: AccountType, name: string) {
try {
const account = {
id: uuidv4(),
type,
name;
};
awaitthis._database.insert(account);
} catch (error) {
console.error(`Anunexpected error occurred while creating an account. Name: ${name}, Error:${error}`);
}
}
private _database:Database<Account>;
}
C#クラスの例:
using System;
using System.Threading.Tasks;
enum AccountType {
Trial,
Basic,
Pro
}
record Account(string Id, AccountType Type, string Name);
interface IDatabase<T> {
Task Insert(T item);
Task<T> Get(stringid);
}
class AccountManager {
publicAccountManager(IDatabase<Account> database) {
_database = database;
}
public async voidCreateAccount(AccountType type, string name) {
try {
var account = newAccount(
Guid.NewGuid().ToString(),
type,
name
);
await_database.Insert(account)
} catch (Exceptionexception) {
Console.WriteLine($"An unexpected error occurred while creating anaccount. Name: {name}, Exception: {exception}");
}
}
IDatabase<Account>_database;
}
3. C#のその他の利点
TypeScript との類似性だけが C#の唯一の利点ではありません。他にも利点があります。
- 3.1 ネイティブコードとの連携が容易
- 3.2 イベント
- 3.3 その他の機能
3.1 ネイティブコードとの連携
C#の最大の利点の一つは、ネイティブコードに深くアクセスできることです。この記事の冒頭で述べたように、TypeScriptはC/C++コードとの連携が得意ではありません。Node.jsにはネイティブC/C++をサポートするプラグインであるNode-APIがありますが、ネイティブ関数用に追加のC++ラッパーを作成し、ネイティブ型をJavaScriptオブジェクトに変換する(またはその逆)必要があり、JNIと同様の動作です。一方、C#はネイティブ関数を直接呼び出すことができ、ライブラリをアプリケーションのbinディレクトリに配置し、APIをC#の外部関数として定義するだけで済みます。その後、外部関数はC#関数と同様に使用でき、.NETランタイムがC#データ型とネイティブデータ型の間の変換を処理します。例えば、ネイティブライブラリが以下のC関数をエクスポートしている場合:
int countOccurrencesOfCharacter(char *string, char character) { int count = 0;
for (int i = 0;string[i] != '\0'; i++) {
if (string[i] ==character) {
count++;
}
}
return count;
}
次のようにC#から呼び出すことができます:
using System;
using System.Runtime.InteropServices;
var count = MyLib.countOccurrencesOfCharacter("C# is prettyneat, eh?", 'e');
// Prints "3"
Console.WriteLine(count);
class MyLib {
// Just placeMyLibraryName.so in the app's bin folder
[DllImport("MyLibraryName")]
public static externint countOccurrencesOfCharacter(string str, char character);
}
この方法により、C接続を介して任意の動的ライブラリ(.so、.dll、または.dylib)にアクセスできます。つまり、C、C++、Rust、Go、その他の言語で書かれたコードを、マシンコードにコンパイルされていれば簡単に呼び出すことができます。ネイティブ連携のその他の応用例としては:
- ポインタを IntPtr としてネイティブオブジェクトに渡す
GetFunctionPointerForDelegate()を使用してC#メソッドを関数ポインタとしてネイティブ関数に渡すMarshal.PtrToStringAnsi()を使用してC文字列をC#文字列に変換する- 構造体や配列を変換する
3.2 イベント
C#のユニークな特性として、第一級のイベントサポートを提供することが挙げられます。TypeScriptでは、addEventListener() メソッドを実装してクライアントがイベントをリッスンできるようにしますが、C#にはeventキーワードがあり、イベントを定義し、簡潔な構文ですべてのリスナーにイベントを通知できます(TypeScriptのようにすべてのイベントリスナーを手動で走査して try/catch ブロックで実行する必要はありません)。例えば、Connection クラスに MessageReceived イベントを次のように定義できます:
class Connection {
// AnAction<string> is a callback that accepts a string parameter.
public eventAction<string> MessageReceived;
}
Connection を使用するコードは、+= 演算子を使用して MessageReceived にハンドラを追加できます:
var connection = new Connection();
connection.MessageReceived += (message) => {
Console.WriteLine("Message was received: " + message);
};
そして、Connection クラスは内部で MessageReceived を呼び出し、すべてのリスナーに対して MessageReceived イベントをトリガーできます:
// Raise the MessageReceived event
MessageReceived?.Invoke(message);
4. その他の利点
- パフォーマンス:
C#は高速です。C#のASP.NET(Core) Webフレームワークは、Techempowerのベンチマークで常に上位を占めており、C#の.NET CoreCLRランタイムのパフォーマンスはメジャーバージョンごとに向上しています。C#が優れたパフォーマンスを持つ理由の一つは、クラスではなく構造体を使用することで、アプリケーションがガベージコレクションを最小限に抑えるか、完全に排除できることです。そのため、C#はビデオゲームプログラミングで非常に人気があります。 - ゲームと複合現実:
C#はゲーム開発で最も人気のある言語の一つであり、Unity、Godot、さらにはUnrealゲームエンジンもC#を使用しています。C#は複合現実でも人気があり、VRおよびARアプリケーションはUnityで作成されています。 C#にはファーストパーティのライブラリ、ツール、ドキュメントがあるため、いくつかのタスクは非常に簡単に実装できます。例えば、C#でgRPCクライアントを作成するのはTypeScriptよりもはるかに便利です。逆に、Node.jsでTypeScriptを使用する場合、正しいモジュールとツールの組み合わせを見つけて、JavaScript gRPCクライアントと対応するTypeScript型を正しく生成する必要があります。- 高度な機能:
C#には、他の言語にはない機能が多数あります。例えば、演算子オーバーロード、デストラクタなどです。
5. まとめ
前述の通り、完璧な言語は存在しません。言語を設計する際には常にトレードオフが必要であり、一部の言語は高速ですが使用難易度が高くなります(例:Rustの借用チェッカー)。一方、非常に使いやすい言語もありますが、通常はパフォーマンスの最適化が難しくなります(例:JavaScriptの動的言語特性)。そのため、私はそれぞれに長所があるが似ていて連携できる、一連の類似言語を習得することが非常に有用だと考えています。例えば、以下は私が選んだ言語のセットです。
5.1 TypeScript
- 最も高レベルの言語で、開発速度が最も速い
- パフォーマンスは最適ではないが、ほとんどのアプリケーションに適用可能
- ネイティブコードとの連携にはあまり適していない
5.2 C#
- 依然として高級言語であり、ガベージコレクションをサポートしているため使いやすいが、
TypeScriptほどではない - 速度とメモリ使用量の点で
TypeScriptより優れている - 最も重要なのは、低レベルとの優れた連携性
C++
- 開発難易度が高い(例:手動メモリ管理が必要)ため、開発速度ははるかに遅くなる
- しかし、実行時パフォーマンスは最高!また、どこでも利用でき、多くの既存ソフトウェアと連携できる
C#に似ており、標準ライブラリも優れているが、多くの落とし穴もある(ほとんどはメモリ管理に関連)。メモリ安全性が優れているためRustを使いたいが、多くの仕事が既存のC++コードと連携する必要があるため、C++を使う方が簡単。
参考リンク:https://nate.org/csharp-and-typescript