author| Nate Hill
translator| meniscus
produced| CSDN(ID:CSDNnews)
TypeScript is very good. It perfectly combines strong typing with rapid development, making it so easy to use that I will default to this library in many cases. However, there is no perfect language in the world, and in some cases TypeScript is not the most appropriate tool:
- Performance is critical (e.g. real-time communications, video games)
- Need to interact with native code such as C/C++ or Rust
- Need for a more stringent type system (e.g., financial system)
For these situations, TypeScript developers are better off choosing other languages. C#, Go and Java are all very good choices. They are much faster than TypeScript, and each language has its own strengths. C#works well with TypeScript, and let me explain why.

1. TypeScript is JavaScript with C#added
C#works well with TypeScript because they look like the same language. Both were designed by Anders Hejlsberg, and in many ways, TypeScript is JavaScript with C#added. Their features and syntax are similar, making it easy to combine the two in the same project. More importantly, the language of C#is very similar to TypeScript, so it is easy for developers to read and write code. In contrast, Go is a completely different language: no classes, no inheritance, no exceptions, no package-level encapsulation (only class-level encapsulation), and a completely different syntax. Of course, this is not necessarily a bad thing, but developers do need to rethink and design code differently, so using Go and TypeScript at the same time can be difficult. However, Java is very similar to C#, but still lacks many of the features that C#and TypeScript have.
2. Similarities between C#and TypeScript
As you may already know, C#and TypeScript have many similarities, such as C-based syntax, classes, interfaces, generics, etc. Below, let me list the similarities between the two in detail:
- 2.1 async/await
- 2.2 Lambda expressions and functional array methods
- 2.3 Used to handle empty operators (?,!,??)
- 2.4 deconstruction
- 2.5 Command Line Interface (CLI)
- 2.6 Basic functions (classes, generics, errors and enumerations)
2.1 async/await
First, both C#and JavaScript use async/await to handle asynchronous code. In JavaScript, asynchronous operations are represented by promises, and applications can await an asynchronous operation. Promise in C#is actually Task, which is completely the same concept as Promise, and has corresponding methods. The following examples demonstrate the use of async/await in two languages:
** Example of async/await in TypeScript: **
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;
}
** Example of async/await in C#: **
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 | Equivalent 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 Lambda expressions and functional array methods
C#和JavaScript都用熟悉的=>语法(即箭头函数)来表示lambda表达式。下面是TypeScript和C#的比较:
** Use lambda expressions in 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');
});
** lambda expression used in 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 | Equivalent 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 Handling empty operators
C#and TypeScript handle null features the same way:
| Feature name | Syntax | Documentation links |
|---|---|---|
| Optional properties | 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 assertion | 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 |
| Optional chaining | 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- |
| Nullish coalescing | 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 deconstruction
Although C#does not support deconstructing arrays or classes by default, it supports deconstructing Tuple and Record, and users can also define deconstructing for custom types. Here are examples of deconstruction in TypeScript and C#:
** Examples of deconstruction in 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;
** Examples of deconstruction in 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 Command Line Interface (CLI)
我的开发方式是使用文本编辑器编写代码,然后在终端运行命令,构建并运行。对于TypeScript,这意味着需要使用node或deno命令行界面(CLI)。C#也有类似的CLI,名为dotnet(由 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 Basic functions (classes, generics, errors and enumerations)
These are the more basic similarities between TypeScript and C#. The following examples are an introduction to these aspects:
** Example of TypeScript class: **
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>;
}
** Example of C#class: **
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. Other advantages of C#
Similarity to TypeScript is not the only advantage of C#, it also has other advantages:
- 3.1 Easier to integrate with native code
- 3.2 event
- 3.3 other functions
3.1 Combined with native code
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;
}
Then you can call it from C#as follows:
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或其他语言编写的代码,只要编译成机器码即可。原生交互的其他应用还有:
- Pass the pointer to the native object as IntPtr
- 利用
GetFunctionPointerForDelegate()将C#方法作为函数指针传给原生函数 - 使用
Marshal.PtrToStringAnsi()将C字符串转换为C#字符串 - Convert structures and arrays
3.2 event
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. other advantages
- 性能:
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. summary
如前所述,世上没有完美的语言。在设计语言时总要有所权衡,所以一些语言的速度更快,但使用难度会增加(例如Rust的借出检查)。另一方面,一些语言非常易用,但通常性能的优化难度就会增加(例如JavaScript的动态语言特性)。正因如此,我相信掌握一组相似的语言会非常有用:这些语言分别有各自的长处,但都很相似,而且能互相配合。例如,下面是我选择的一组语言:
5.1 TypeScript
- The highest level language, the fastest development speed
- Performance is not optimal, but works for most applications
- Not suitable for combining with native code
5.2 C#
- 仍然是高级语言,支持垃圾回收,所以很容易使用,尽管并不如
TypeScript那么容易。 - 从速度和内存占用量来看,其性能都优于
TypeScript - The most important thing is that it can integrate well with the bottom
C++
- Development is difficult (e.g. manual memory management is required), so the development speed will be much slower
- But it has the best performance at runtime! It is available anywhere and can be combined with many existing software
- 很像
C#,而且标准库很好,但也有许多陷阱(大多数与内存管理有关)。我更希望使用Rust,因为它的内存安全性更好,但我的许多工作都要与已有的C++代码结合,因此使用C++会更容易。
Reference link: nate.org/csharp-and-typescript