Generic
Generic — Khái niệm và cú pháp
Generic function, class, interface, method — cú pháp C# và TypeScript song song.
1. Generic function
// T là type parameter
public static T Identity<T>(T x) => x;
Identity<int>(42); // explicit
Identity(42); // inferred — T = int
Identity("hello"); // T = string
// Nhiều type parameter
public static (A, B) Pair<A, B>(A a, B b) => (a, b);
var p = Pair(1, "one"); // (int, string)
// T trong <>
function identity<T>(x: T): T {
return x;
}
identity<number>(42); // explicit
identity(42); // inferred
identity("hello");
// Nhiều type parameter
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const p = pair(1, "one"); // [number, string]
2. Generic class
public class Box<T> {
public T Value { get; }
public Box(T value) => Value = value;
public Box<U> Map<U>(Func<T, U> fn) => new Box<U>(fn(Value));
}
var box = new Box<int>(42);
var stringBox = box.Map(n => n.ToString()); // Box<string>
class Box<T> {
constructor(public readonly value: T) {}
map<U>(fn: (x: T) => U): Box<U> {
return new Box(fn(this.value));
}
}
const box = new Box<number>(42);
const stringBox = box.map(n => n.toString()); // Box<string>
3. Generic interface
public interface IRepository<T> where T : class {
Task<T?> GetByIdAsync(int id);
Task<List<T>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
public class UserRepository : IRepository<User> {
public Task<User?> GetByIdAsync(int id) { ... }
// ...
}
interface Repository<T extends { id: number }> {
getById(id: number): Promise<T | undefined>;
getAll(): Promise<T[]>;
add(entity: T): Promise<void>;
update(entity: T): Promise<void>;
delete(id: number): Promise<void>;
}
class UserRepository implements Repository<User> {
async getById(id: number) { return undefined; }
// ...
}
4. Generic method trong class non-generic
public class Cache {
private readonly Dictionary<string, object> _data = new();
public void Set<T>(string key, T value) => _data[key] = value!;
public T? Get<T>(string key) {
if (_data.TryGetValue(key, out var v) && v is T t) return t;
return default;
}
}
var c = new Cache();
c.Set("count", 42);
int n = c.Get<int>("count"); // 42
5. Default type parameter
C# không có default type parameter (như TS). Phải overload hoặc dùng helper.
// Workaround: 2 method
public class Box {
public static Box<object> Create() => new();
public static Box<T> Create<T>() => new();
}
interface ApiResponse<TData = unknown, TError = Error> {
data: TData;
error?: TError;
}
const r1: ApiResponse = { data: "x" }; // TData = unknown
const r2: ApiResponse<User> = { data: { id: 1, name: "A" } };
6. Type inference
Cả 2 ngôn ngữ đều infer type parameter từ argument:
public static T First<T>(IEnumerable<T> items) => items.First();
First(new[] { 1, 2, 3 }); // T = int (inferred)
function first<T>(items: T[]): T { return items[0]; }
first([1, 2, 3]); // T = number
Khi inference fail — bạn cần specify thủ công:
function create<T>(): T[] { return []; }
create(); // T = unknown (không có gợi ý nào)
create<number>(); // ✅
7. Generic & polymorphism
Generic là dạng parametric polymorphism. Khác subtype polymorphism (kế thừa, override):
// Subtype polymorphism
Animal[] zoo = { new Dog(), new Cat() };
// Parametric polymorphism
T Identity<T>(T x) => x;
List<int> ints; List<string> strings;
Modern code thường kết hợp 2: IRepository<T> (parametric) với T : class (constraint kiểu subtype).
8. Self-referencing generic (Curiously Recurring Template Pattern)
public interface IComparable<T> {
int CompareTo(T other);
}
public class MyNumber : IComparable<MyNumber> {
public int Value;
public int CompareTo(MyNumber other) => Value.CompareTo(other.Value);
}
// Builder fluent với generic self-type
public abstract class FluentBuilder<TSelf> where TSelf : FluentBuilder<TSelf> {
public TSelf SetName(string n) {
// ...
return (TSelf)this;
}
}
public class UserBuilder : FluentBuilder<UserBuilder> {
public UserBuilder SetEmail(string e) { ... return this; }
}
new UserBuilder()
.SetName("Alice") // trả UserBuilder (không phải FluentBuilder!)
.SetEmail("a@b.com"); // SetEmail vẫn gọi được
9. Generic và collection trong .NET / TS
List<int> nums = new() { 1, 2, 3 };
Dictionary<string, int> ages = new() { ["Alice"] = 25 };
HashSet<string> tags = new() { "vue", "nuxt" };
Queue<Task> jobs = new();
Stack<string> path = new();
Mọi collection chuẩn từ .NET 2.0 đều generic — tránh boxing với value type.
const nums: number[] = [1, 2, 3];
const ages = new Map<string, number>([["Alice", 25]]);
const tags = new Set<string>(["vue", "nuxt"]);
// Queue/Stack: dùng array (push/shift, push/pop)
10. typeof parameter trong TS (đặc thù)
const colors = ["red", "green", "blue"] as const;
type Color = typeof colors[number]; // "red" | "green" | "blue"
function pick<T extends readonly unknown[]>(arr: T): T[number] {
return arr[Math.floor(Math.random() * arr.length)];
}
const c = pick(colors); // "red" | "green" | "blue"
C# không có cơ chế tương đương ở compile-time (cần reflection runtime).
Tiếp theo: Constraint & Variance