Generic
Generic — C# (Reified) vs TypeScript (Erased)
Khác biệt cốt lõi runtime — C# generic giữ type info, TypeScript xoá hết. Implication thực tế.
Tổng quan — Reified vs Erased
| C# / Java (modern) | TypeScript / Java (legacy) | |
|---|---|---|
| Generic info ở runtime | ✅ Reified | ❌ Erased |
typeof(List<int>) runtime | Khác typeof(List<string>) | Cùng Array |
| Tạo instance trong generic | ✅ new T() (với constraint) | ❌ Không trực tiếp |
| Overload theo generic type | ✅ Khác Foo(int) vs Foo(string) | – |
| Reflection generic | ✅ Đầy đủ | – |
| Performance value type | Không boxing | N/A (TS chỉ compile-time) |
⭐ Câu PV đặc thù:"C# generic và TypeScript generic khác nhau ra sao?"
"C# generic là reified — runtime giữ thông tin type cụ thể.
List<int>vàList<string>là 2 type khác nhau ở runtime, có thể overload theo type này, dùng reflection inspect.TypeScript generic là erased — chỉ tồn tại compile-time. Sau khi compile sang JS, mọi generic biến mất. Không thể
T extends Animal ? new T() : ...ở runtime — JS không biết T là gì."
1. Code minh hoạ khác biệt
// Có thể tạo instance từ T (cần constraint new())
public class Factory<T> where T : new() {
public T Create() => new T();
}
// Có thể inspect type T ở runtime
public class Logger<T> {
public void Log(T value) {
Console.WriteLine($"Type: {typeof(T).Name}");
Console.WriteLine($"Value: {value}");
}
}
var l = new Logger<int>();
l.Log(42);
// Type: Int32
// Value: 42
// typeof so sánh cụ thể
Console.WriteLine(typeof(List<int>) == typeof(List<string>)); // False
// Không thể new T() trực tiếp
class Factory<T> {
// create(): T { return new T(); } // ❌ Compile error
// Workaround: nhận constructor làm parameter
create(Ctor: new () => T): T {
return new Ctor();
}
}
// Không inspect được T runtime
class Logger<T> {
log(value: T) {
// console.log(typeof T); // ❌ Không có cú pháp
console.log(typeof value); // ✅ JS typeof — chỉ "string", "number"...
console.log(value);
}
}
// Sau compile, JS thấy:
// class Logger {
// log(value) { ... } ← T biến mất
// }
2. Hệ quả thực tế
A. Tạo instance từ generic
// C# — OK với new() constraint
public T CreateNew<T>() where T : new() => new T();
// TS — phải truyền constructor
function createNew<T>(Ctor: new () => T): T {
return new Ctor();
}
createNew(User); // OK
B. Check type runtime
// C# — typeof
public void Handle<T>(T value) {
if (typeof(T) == typeof(string)) Console.WriteLine("string!");
if (value is User u) Console.WriteLine(u.Name);
}
// TS — không check generic, chỉ check value
function handle<T>(value: T) {
// if (T === string) // ❌
if (typeof value === "string") console.log("string!");
if (value && typeof value === "object" && "name" in value) {
// narrow
}
}
C. Overload theo generic type
// C# — overload theo type param
public class Cache {
public string Get<T>() where T : class => "ref";
// public string Get<T>() where T : struct => "val"; // ❌ Constraint không phân biệt overload — sẽ là cách khác
}
// Workaround: dùng `is`
public string GetTypeName<T>(T value) {
return value switch {
string => "string",
int => "int",
_ => "object"
};
}
// TS — function overload declaration
function get(t: "string"): string;
function get(t: "number"): number;
function get(t: string): any { /* impl */ }
D. Cast / as
// C# — runtime check
object o = "hello";
string s = o as string; // s = "hello"
int? n = o as int?; // n = null (runtime check)
// TS — compile-time only
const o: unknown = "hello";
const s = o as string; // compile pass, không check runtime
const n = o as number; // ⚠️ pass compile, sai logic
Bẫy TS lớn:
as không validate runtime. Để safe runtime → dùng Zod / Valibot / io-ts validate sau parse JSON.3. Performance — boxing
C# generic = no boxing với value type.TS: không có khái niệm value type, mọi thứ là object → không có boxing.
List<int> ints = new(); // Lưu int thẳng trên heap array
ArrayList list = new(); // ❌ Lưu object — mỗi int phải BOX
list.Add(1); // boxing → object trên heap
// C# generic compile ra **specialized IL** cho mỗi value type instantiation
// → List<int> implementation # List<long> implementation
4. Variance khác biệt
- C#: variance phải khai báo explicit (
out T,in T). Chỉ cho interface và delegate. - TS: variance auto-inferred. Có thể annotate explicit
out/in(4.7+) nhưng không bắt buộc.
5. Generic constraint — khả năng
| C# | TS | |
|---|---|---|
T : class (ref type) | ✅ | T extends object |
T : struct (value type) | ✅ | – |
T : new() | ✅ | – (truyền Ctor param) |
T : Interface | ✅ | T extends Interface |
T : BaseClass | ✅ | T extends BaseClass |
T : enum (C# 7.3+) | ✅ | – |
T : INumber<T> (C# 11+) | ✅ | – (use number) |
T : U (linked params) | ✅ | T extends U |
keyof T | – | ✅ |
T extends "literal" | – | ✅ |
Conditional T extends X ? A : B | – | ✅ |
infer trong constraint | – | ✅ |
| Mapped types | – | ✅ |
| Template literal types | – | ✅ |
⭐ TS có type-level programming mạnh hơn C# rất nhiều — conditional, mapped, infer, template literal. Đổi lại C# có runtime info đầy đủ.
6. Reflection
// C# — full runtime introspection
var type = typeof(List<int>);
foreach (var arg in type.GetGenericArguments()) {
Console.WriteLine(arg.Name); // Int32
}
// Tạo instance runtime
var generic = typeof(List<>).MakeGenericType(typeof(string));
var instance = Activator.CreateInstance(generic); // List<string>
// TS — không có. Phải dùng decorator metadata (TC39 stage 3, stable 5.9)
class User {}
// reflect-metadata package + decorator để emit metadata runtime
7. Khi nào điều này thực sự quan trọng?
Scenario cần Reified (C#):
- Generic Factory với
new(). - Reflection-based ORM (EF Core, Dapper).
- JSON serializer biết type chính xác để map.
- Dependency Injection resolve
IRepository<User>runtime.
- Type safety compile-time.
- API client typed.
- Form validation.
- Mọi web app frontend / Node backend tiêu chuẩn.
8. Pattern thay thế trong TS cho thiếu reified
A. Class as runtime token
class User { id = 0; name = ""; }
function create<T>(Ctor: new () => T): T {
return new Ctor();
}
const u = create(User); // typed User
B. Schema-first
import { z } from "zod";
const UserSchema = z.object({ id: z.number(), name: z.string() });
type User = z.infer<typeof UserSchema>;
const u = UserSchema.parse(jsonData); // runtime validate + type infer
C. Decorator + metadata (NestJS, TypeORM style)
@Entity()
class User {
@PrimaryGeneratedColumn() id!: number;
@Column() name!: string;
}
Decorator emit metadata reflect-metadata package — ORM dùng runtime.
Tiếp theo: Câu hỏi phỏng vấn Generic