Với <out T>
, bạn có thể coi tham chiếu giao diện như một tham chiếu trở lên trong hệ thống phân cấp.
Với <in T>
, bạn có thể coi tham chiếu giao diện như một tham chiếu hướng xuống trong quá trình tìm kiếm.
Hãy để tôi cố gắng giải thích nó bằng nhiều thuật ngữ tiếng Anh hơn.
Giả sử bạn đang truy xuất danh sách các loài động vật từ vườn thú của mình và bạn định xử lý chúng. Tất cả động vật (trong vườn thú của bạn) đều có tên và ID duy nhất. Một số động vật là động vật có vú, một số là bò sát, một số là lưỡng cư, một số là cá, v.v. nhưng chúng đều là động vật.
Vì vậy, với danh sách động vật của bạn (bao gồm các loài động vật khác nhau), bạn có thể nói rằng tất cả các loài động vật đều có tên, vì vậy rõ ràng là sẽ an toàn nếu lấy tên của tất cả các loài động vật.
Tuy nhiên, nếu bạn chỉ có một danh sách các loài cá, nhưng cần phải đối xử với chúng như động vật, thì điều đó có hiệu quả không? Theo trực giác, nó sẽ hoạt động, nhưng trong C # 3.0 trở về trước, đoạn mã này sẽ không biên dịch:
IEnumerable<Animal> animals = GetFishes();
Lý do cho điều này là trình biên dịch không "biết" bạn dự định hoặc có thể làm gì với bộ sưu tập động vật sau khi bạn đã truy xuất nó. Đối với tất cả những gì nó biết, có thể có một cách IEnumerable<T>
để đưa một đối tượng trở lại danh sách và điều đó có khả năng cho phép bạn đưa một con vật không phải là cá vào một bộ sưu tập được cho là chỉ chứa cá.
Nói cách khác, trình biên dịch không thể đảm bảo rằng điều này không được phép:
animals.Add(new Mammal("Zebra"));
Vì vậy, trình biên dịch hoàn toàn từ chối biên dịch mã của bạn. Đây là hiệp phương sai.
Hãy nhìn vào sự tương phản.
Vì vườn thú của chúng ta có thể xử lý tất cả các loài động vật, nó chắc chắn có thể xử lý cá, vì vậy chúng ta hãy thử thêm một số loài cá vào vườn thú của chúng ta.
Trong C # 3.0 trở về trước, điều này không biên dịch:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Ở đây, trình biên dịch có thể cho phép đoạn mã này, mặc dù phương thức trả về List<Animal>
đơn giản vì tất cả các loài cá đều là động vật, vì vậy nếu chúng ta chỉ thay đổi các loại thành này:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Sau đó, nó sẽ hoạt động, nhưng trình biên dịch không thể xác định rằng bạn không cố gắng làm điều này:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
Vì danh sách thực sự là danh sách động vật, điều này không được phép.
Vì vậy, tương phản và đồng phương sai là cách bạn xử lý các tham chiếu đối tượng và những gì bạn được phép làm với chúng.
Các từ khóa in
và out
trong C # 4.0 đặc biệt đánh dấu giao diện là giao diện này hoặc giao diện khác. Với in
, bạn được phép đặt kiểu chung (thường là T) trong các vị trí đầu vào , có nghĩa là đối số phương thức và thuộc tính chỉ ghi.
Với out
, bạn được phép đặt kiểu chung trong các vị trí đầu ra , là giá trị trả về của phương thức, thuộc tính chỉ đọc và tham số phương thức ra.
Điều này sẽ cho phép bạn thực hiện những gì dự định làm với mã:
IEnumerable<Animal> animals = GetFishes();
List<T>
có cả hướng vào và hướng ra trên T, vì vậy nó không phải là đồng biến thể cũng không phải là biến thể đối lập, mà là một giao diện cho phép bạn thêm các đối tượng, như sau:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
sẽ cho phép bạn làm điều này:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Dưới đây là một số video thể hiện các khái niệm:
Đây là một ví dụ:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Nếu không có những dấu này, phần sau có thể biên dịch:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
hoặc cái này:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants