CodeGym /Các khóa học /C# SELF /Default-methods trong interface

Default-methods trong interface

C# SELF
Mức độ, Bài học
Có sẵn

1. Từ "hợp đồng thuần" đến kiến trúc linh hoạt

Trước C# 8, interface giống như hợp đồng cứng nhắc: muốn implement interface thì phải implement hết mọi thứ. Nếu có thêm thành viên mới, tất cả các class đã implement phải sửa ngay, không thì compiler không cho build.

Nhưng đời không đơn giản vậy. Giả sử bạn maintain một thư viện mà hàng trăm project dùng, và tự nhiên bạn cần thêm method mới vào interface. Không muốn phá backward compatibility đúng không? Đây là lúc default-methods (Default Interface Methods, DIM) xuất hiện!

Bản chất là gì?

Default-methods cho phép bạn khai báo implementation của method ngay trong interface. Hợp đồng giờ linh hoạt hơn: nếu class không implement "hàng mới", sẽ dùng implementation mặc định. Kiểu như cascadeur đóng thế trong phim: diễn viên không nhảy cầu thì cascadeur nhảy thay.

2. Cú pháp Default Interface Methods

Khai báo method có implementation trong interface như nào?

Rất giống method bình thường, chỉ là giờ bạn có thể (và nên) viết body method ngay trong interface:


public interface ILogger
{
    void Log(string message);

    // Method mới có implementation mặc định!
    void LogWarning(string message)
    {
        Log("[WARNING] " + message);
    }
}

Ở đây LogWarning đã có implementation! Bất kỳ class nào implement ILogger chỉ cần implement Log, còn LogWarning sẽ dùng mặc định (trừ khi tự override).

So sánh: signature cổ điển vs. hiện đại

Phiên bản Khai báo trong interface
Trước C# 8
void DoSomething();
C# 8 trở lên
void DoSomething() { Console.WriteLine("Đang làm việc!"); }

Chi tiết cú pháp quan trọng

  • Method có implementation thì phải có body trong dấu ngoặc nhọn.
  • Default-methods không được là abstract.
  • Tất cả method trong interface vẫn ngầm định là public.
  • Có thể khai báo property với get/set mặc định (xem bên dưới).

3. Ví dụ thực tế

Ví dụ 1. Đảm bảo backward compatibility

Giả sử app của bạn có interface để lưu dữ liệu:


public interface ISaveable
{
    void Save(string filePath);
}

Sau này bạn muốn thêm chức năng lưu lên cloud. Không muốn sửa cả trăm class? Thêm default-method thôi!


public interface ISaveable
{
    void Save(string filePath);

    // Method mới với implementation "mặc định"!
    void SaveToCloud(string cloudService)
    {
        Console.WriteLine($"Lưu lên cloud {cloudService} (mặc định — không làm gì cả)");
    }
}

Giờ tất cả class cũ tự động "biết" lưu lên cloud (dù chỉ in ra message).

Ví dụ 2. Mở rộng interface logger

Trước đó ta có interface logger đơn giản:


public interface ILogger
{
    void Log(string message);
}

Thêm default-method để log lỗi:


public interface ILogger
{
    void Log(string message);

    void LogError(string message)
    {
        Log("[ERROR] " + message);
    }
}

Class implement ILogger không cần implement LogError — sẽ dùng version mặc định:


public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
    // Không implement LogError — sẽ dùng mặc định!
}

ILogger logger = new ConsoleLogger();
logger.Log("Mọi thứ ổn!");
logger.LogError("Ôi không, có lỗi rồi!"); // Gọi implementation mặc định

Ví dụ 3. Default-methods + app mở rộng

App của bạn hỗ trợ nhiều kiểu export: ra file, ra DB, ra network. Interface:


public interface IExporter
{
    void Export(string data, string destination);

    // Chức năng mới — export ra archive
    void ExportToArchive(string data, string archivePath)
    {
        Console.WriteLine("Mặc định không hỗ trợ archive.");
    }
}

Plugin do dev khác viết vẫn chạy bình thường, dù không biết gì về method mới.

4. Default-methods trong interface được gọi như nào?

Kịch bản "class cũ — interface mới"

Nếu class không implement default-method của interface, khi gọi qua reference interface sẽ dùng implementation trong interface. Nếu có implement — sẽ dùng của class.


public class FileExporter : IExporter
{
    public void Export(string data, string destination)
    {
        Console.WriteLine("Lưu ra file...");
    }
    // Không implement ExportToArchive — sẽ dùng mặc định
}

IExporter exporter = new FileExporter();
exporter.Export("dữ liệu", "file.txt");        // Dùng implementation của FileExporter
exporter.ExportToArchive("dữ liệu", "file.zip"); // Dùng implementation mặc định!

Kịch bản "class override default-method"


public class AdvancedExporter : IExporter
{
    public void Export(string data, string destination)
    {
        Console.WriteLine("Lưu ở chế độ nâng cao...");
    }

    public void ExportToArchive(string data, string archivePath)
    {
        Console.WriteLine("Đã hỗ trợ archive!");
    }
}

IExporter exporter = new AdvancedExporter();
exporter.ExportToArchive("dữ liệu", "file.zip"); // Gọi implementation của class!

5. Còn gì nữa với Default Interface Methods?

Property và event mặc định

Có thể khai báo property với implementation mặc định nếu có body get hoặc set:


public interface IHasId
{
    // Tự động trả về 42, trừ khi override
    int Id => 42;
}

public class Person : IHasId {}
Console.WriteLine(new Person().Id); // 42

Gọi default-method từ code trong interface

Trong interface, default-method và các thành viên khác có thể gọi nhau:


public interface IDemo
{
    void Foo() => Bar();
    void Bar() => Console.WriteLine("BAR");
}

6. Giới hạn và đặc điểm của Default Interface Methods

Có khai báo field, constructor được không?

Không. Dù có default-method, interface vẫn không phải class. Không có field, constructor, destructor.

Có dùng base với interface được không?

Có, nhưng phải cẩn thận. Trong default-method của interface, bạn có thể gọi method của interface cha qua chỉ định rõ ràng:


public interface IBase
{
    void Greet() => Console.WriteLine("Hello từ IBase");
}

public interface IDerived : IBase
{
    void IBase.Greet()
    {
        Console.WriteLine("Xin chào từ IDerived!");
        IBase.Greet(this); // Gọi method của interface cha rõ ràng
    }
}

Nhưng thường không cần cho các trường hợp cơ bản.

Nếu có conflict giữa default implementation thì sao?


public interface IA { void Foo() { Console.WriteLine("A"); } }
public interface IB { void Foo() { Console.WriteLine("B"); } }

// Class không implement Foo rõ ràng:
public class C : IA, IB { }
// Lỗi compile: không biết chọn implementation nào!

7. Lỗi thường gặp, giới hạn và đặc điểm

Lỗi vui thường gặp:
Nhiều bạn thử khai báo field trong interface sau khi biết DIM — nhưng vẫn không được. Nếu bạn thử implement static-method mặc định — trước C# 8 là không thể. (Từ C# 8 trở đi có thể có static-method trong interface, nhưng default implementation cho static-method là chủ đề khác.)

Đặc điểm: Diamond Problem ("vấn đề hình thoi")
Nếu class implement hai interface đều có default-method giống nhau, bạn phải tự implement method đó:


public class ConflictClass : IA, IB
{
    public void Foo() // tự chọn version!
    {
        // Gọi implementation mong muốn qua interface (nếu cần)
        ((IA)this).Foo();
        // hoặc
        ((IB)this).Foo();
    }
}

Đừng lạm dụng!
Default-method cứu backward compatibility, nhưng lạm dụng sẽ làm kiến trúc "bẩn", logic bị phân tán vào interface. Hãy giữ phần quan trọng trong class, interface chỉ nên là "hợp đồng" thực sự.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Mở rộng interface với phương thức mặc định
Mở rộng interface với phương thức mặc định
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Xung đột interface và chọn cách triển khai
Xung đột interface và chọn cách triển khai
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION