C# 是 TypeScript 的最佳替补?
TypeScript非常优秀。它完美地结合了强类型和快速开发,因此非常好用,我在许多情况下都会默认选择这个库。但是,世上没有完美的语言,有些情况下TypeScript并不是最合适的工具:
-
性能至关重要(例如实时通信、视频游戏) -
需要与原生代码(如C/C++或Rust)交互 -
需要更严格的类型系统(例如金融系统)
TypeScript 就是添加了 C# 的 JavaScript
C#和TypeScript的相似之处
-
async/await -
lambda表达式和函数式数组方法 -
用于处理空的操作符(?,!,??) -
解构 -
命令行界面(CLI)
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;
}
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 API |
等价的C# API |
Promise.all() |
Task.WaitAll() |
Promise.resolve() |
Task.FromResult() |
Promise.reject() |
Task.FromException() |
Promise.prototype.then() |
Task.ContinueWith() |
new Promise() |
new TaskCompletionSource() |
Lambda表达式和函数式数组方法
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');
});
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");
});
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() |
处理空操作符
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 |
解构
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;
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);
命令行界面(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
基本功能(类、泛型、错误和枚举)
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>;
}
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;
}
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;
}
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);
}
-
将指针作为IntPtr传给原生对象 -
利用GetFunctionPointerForDelegate()将C#方法作为函数指针传给原生函数 -
使用Marshal.PtrToStringAnsi()将C字符串转换为C#字符串 -
转换结构和数组
事件
class Connection {
// AnAction<string> is a callback that accepts a string parameter.
public eventAction<string> MessageReceived;
}
var connection = new Connection();
connection.MessageReceived += (message) => {
Console.WriteLine("Message was received: " + message);
};
// Raise the MessageReceived event
MessageReceived?.Invoke(message);
其他优势
-
性能:C#很快。C#的ASP.NET 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#有许多其他语言没有的功能,如运算符重载、析构函数等。
总结
最高层的语言,开发速度最快
性能并非最佳,但适用于大多数应用
不太适合与原生代码结合
仍然是高级语言,支持垃圾回收,所以很容易使用,尽管并不如TypeScript那么容易。
从速度和内存占用量来看,其性能都优于 TypeScript
最重要的是,能够与底层很好地结合
C++
开发难度较大(例如需要手动内存管理),因此开发速度会慢很多
但运行时的性能最佳!而且随处可用,能与许多已有的软件相结合
很像C#,而且标准库很好,但也有许多陷阱(大多数与内存管理有关)。我更希望使用Rust,因为它的内存安全性更好,但我的许多工作都要与已有的C++代码结合,因此使用C++会更容易。
VS Code · 编程开发 · 业界资讯