1. Phân loại các interface chính
Nếu bạn nghĩ interface chỉ là đồ chơi cho mấy ông kiến trúc sư và "clean code", còn thực tế thì không cần, thì mình phải làm bạn bất ngờ: bất kỳ project nghiêm túc nào trên C# đều dính chặt với interface. Tại sao? Vì gần như mọi phần của thư viện chuẩn .NET đều được xây dựng qua interface! Không có chúng thì bạn không thể làm việc với collection, đọc file, thậm chí filter collection bằng LINQ cũng bó tay.
Interface là nền tảng của polymorphism và khả năng mở rộng trong .NET, và chính chúng quyết định cách các "khối lego" framework ghép lại với nhau.
Thư viện chuẩn .NET thực sự ngập tràn interface. Để không bị chìm trong biển này, mình đề xuất phân loại sau (tất nhiên là chưa đầy đủ — sống cả đời cũng không hết!):
| Phân loại | Interface | Dùng để làm gì? |
|---|---|---|
| Collection | , , , , |
Duyệt, thay đổi, truy cập theo index, làm việc với cặp key-value |
| Làm việc với resource | |
Giải phóng resource (file, connection, stream) |
| So sánh | , , |
Sắp xếp, so sánh, uniqueness của object |
| Serialization | |
Biến object thành stream byte (và ngược lại) |
| LINQ và truy vấn | , |
Hỗ trợ truy vấn phức tạp (ví dụ truy vấn DB) |
| Async | , |
Duyệt async và giải phóng resource async |
| Event và notification | , |
Phản ứng khi property/collection thay đổi |
| Ngày giờ | |
Format string custom |
| Cấu trúc dữ liệu | , |
So sánh sâu collection và tuple |
| Stream/input-output | , , , |
Làm việc với stream, push/pull notification |
Lưu ý! Nhiều interface ở trên có type parameter: List<string>. Thường dùng trong collection và interface collection Int[] → List<int>. Chi tiết sẽ bàn ở mấy level sau khi học về collection.
2. Interface collection
IEnumerable và IEnumerator — vé vào foreach
Gần như mọi collection trong .NET đều implement interface IEnumerable hoặc version generic IEnumerable<T>. Chính interface này cho phép bạn dùng cú pháp thần thánh foreach:
List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // chạy được vì List<int> implement IEnumerable<int>
{
Console.WriteLine(n);
}
Đây là version tối giản của interface này:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator — chính là interface của "người duyệt", chịu trách nhiệm đi qua collection của bạn:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
Trong thực tế bạn rất hay truyền tham số và trả về kiểu IEnumerable<T>. Ví dụ, method trả về tất cả số chẵn trong mảng:
public IEnumerable<int> GetEvenNumbers(int[] array)
{
foreach (var x in array)
if (x % 2 == 0) yield return x;
}
Đúng rồi, từ khóa yield làm phép thuật — tự implement interface cho bạn, chi tiết sẽ nói sau.
ICollection và IList — collection có index và thay đổi được
Nếu bạn cần không chỉ duyệt mà còn thêm/xóa phần tử hoặc lấy theo index, thì cần interface chuyên biệt hơn:
- ICollection<T> — thêm method Add, Remove, và property Count.
- IList<T> — mở rộng thêm thao tác, gồm truy cập theo index (this[int index]).
public void PrintFirstItem(IList<string> list)
{
if (list.Count > 0)
Console.WriteLine(list[0]);
}
List<T> implement cả IEnumerable<T>, ICollection<T> và IList<T>. Rất tiện — bạn có thể dùng nó như list đơn giản hoặc tận dụng hết method của nó.
IDictionary<TKey, TValue> — cặp "key-value"
Nếu bạn thích làm việc với dictionary (mà chuyện này xảy ra rất thường xuyên!), hãy dùng interface IDictionary<TKey, TValue>. Nó đảm bảo bạn có thể lấy value theo key và ngược lại.
public void PrintAllPairs(IDictionary<string, int> ages)
{
foreach (var pair in ages)
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
Ở đây ages có thể là bất cứ gì, Dictionary<string, int> hay SortedDictionary<string, int> — miễn là chúng implement interface này!
3. Interface IDisposable: làm việc đúng với resource
Toàn bộ input-output, làm việc với file, kết nối mạng, database trong .NET đều dựa vào interface IDisposable. Interface này định nghĩa contract cực kỳ quan trọng: nếu object có resource không được quản lý, phải "dọn dẹp" sau khi dùng xong. Đúng rồi, chính nó làm nên cú pháp using:
using (StreamReader reader = new StreamReader("file.txt"))
{
// Làm việc với file, sau using file chắc chắn được đóng!
string line = reader.ReadLine();
}
Interface này trông cực kỳ đơn giản:
public interface IDisposable
{
void Dispose();
}
Nhưng bất kỳ thư viện "nghiêm túc" nào cũng implement nó! Đọc thêm về best practice với interface này ở tài liệu chính thức.
4. Interface cho so sánh và uniqueness
IComparable<T> và IComparer<T>
Nếu bạn muốn collection object của mình có thể sort, object phải biết tự so sánh với nhau. Đó là lý do có interface IComparable<T>:
public class Student : IComparable<Student>
{
public string Name { get; set; }
public int Score { get; set; }
public int CompareTo(Student? other)
{
// Sắp xếp giảm dần theo điểm
if (other == null) return 1;
return other.Score.CompareTo(this.Score);
}
}
// Giờ có thể sort student:
var students = new List<Student> { ... };
students.Sort();
Xem thêm trong tài liệu Microsoft.
IComparer<T> cho phép định nghĩa so sánh ngoài class — ví dụ, bạn muốn đôi khi sort student theo tên, đôi khi theo điểm:
public class NameComparer : IComparer<Student>
{
public int Compare(Student? x, Student? y)
{
return string.Compare(x?.Name, y?.Name);
}
}
IEquatable<T> — so sánh bằng nhau
Muốn object của bạn dùng được trong HashSet<T> (tức là "unique" trong collection)? Hãy implement IEquatable<T>:
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public bool Equals(Person? other)
{
if (other == null) return false;
return this.Name == other.Name;
}
}
5. Interface cho event và notification
Bạn từng muốn chương trình của mình "biết" khi property của object thay đổi chưa? Ví dụ, bạn muốn UI tự động update khi user đổi họ tên? Đó là lý do có interface INotifyPropertyChanged. Nó rất phổ biến trong app giao diện (ví dụ WPF hoặc Xamarin).
Signature của interface rất đơn giản:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler? PropertyChanged;
}
Ví dụ implement:
public class User : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string name;
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
}
Giờ mọi binding UI tới object này sẽ tự động update khi Name thay đổi. Nếu bạn quan tâm — đọc tài liệu chính thức.
6. Một số interface hữu ích khác
Interface IFormattable: format linh hoạt
Khi bạn muốn object của mình có thể in ra string đẹp và đa dạng (ví dụ số chữ số sau dấu phẩy khác nhau), hãy implement interface IFormattable:
public class Temperature : IFormattable
{
public double Celsius { get; }
public Temperature(double celsius) => Celsius = celsius;
public string ToString(string? format, IFormatProvider? formatProvider)
{
// Không phức tạp hóa, chỉ hiển thị 'C' hoặc 'F' tùy format
if (format == "F")
return $"{Celsius * 9 / 5 + 32} F";
return $"{Celsius} C";
}
}
Giờ bạn có thể gọi temp.ToString("F", null) hoặc temp.ToString("C", null). Đó là lý do DateTime, double, decimal và các kiểu built-in khác hỗ trợ format linh hoạt.
Interface cho async
Khi async xuất hiện trong C#, cần interface mới cho thế giới async. Ví dụ, nếu object của bạn giải phóng resource async — hãy implement IAsyncDisposable:
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
Và giờ thay vì using bạn viết await using!
Với async enumeration (await foreach) thì dùng interface IAsyncEnumerable<T>, cho phép duyệt phần tử mà không block main thread. Dùng khi đọc file lớn từng phần hoặc stream data từ internet. Chi tiết ở level 58 :P
Interface serialization: ISerializable
Nếu bạn cần biến object thành stream byte để lưu/truyền (ví dụ qua mạng), .NET có interface ISerializable. Dạo này ít dùng hơn vì có cách tiện hơn (dùng attribute và serializer có sẵn), nhưng vẫn đáng nhắc. Đây là signature:
public interface ISerializable
{
void GetObjectData(SerializationInfo info, StreamingContext context);
}
7. Dùng interface thực tế: ứng dụng thật
Giả sử bạn đang code app console quản lý sách cho thư viện (ừ thì, ai đó phải làm mà!). Bạn muốn in danh sách sách, sort, filter, load và save data. Nhờ biết interface .NET bạn sẽ:
- Trả về nhiều loại collection khác nhau qua IEnumerable<Book>, để user không phải quan tâm là mảng hay list.
- Thêm sort theo nhiều tiêu chí: implement IComparable<Book> để sort theo tên, và IComparer<Book> riêng cho tác giả.
- Giải phóng resource hiệu quả khi làm việc với file qua IDisposable.
- Lọc sách theo thể loại hoặc tác giả bằng LINQ, cái gì implement IEnumerable<T> đều dùng được.
- Scale app dễ dàng — ví dụ, thay file storage bằng database, nếu class của bạn làm việc qua interface (IBookStorage), không phải qua implementation cụ thể.
8. Lỗi phổ biến và lưu ý
Một trong những lỗi newbie hay gặp nhất — dùng implementation cụ thể ("List") thay vì interface ("IEnumerable", "IList") khi khai báo biến và tham số method. NHỚ NHÉ: nếu bạn "code trên interface", code của bạn sẽ linh hoạt và dễ mở rộng.
Đôi khi cần chú ý chọn đúng level interface — ví dụ, nếu bạn viết method chỉ cần duyệt, hãy dùng IEnumerable<T>, đừng dùng IList<T> để tránh ràng buộc không cần thiết.
Một lưu ý nữa: nếu class implement nhiều interface, và giữa chúng có method/property trùng tên, bạn phải dùng explicit implementation, không thì compiler sẽ không hiểu bạn muốn gì.
GO TO FULL VERSION