2011-08-01 1 views
9

Tôi có hai lớp hợp đồng kinh doanh:đại biểu Action, Generics, hiệp phương sai và contravariance

public BusinessContract 

public Person : BusinessContract 

Trong một lớp I có đoạn mã sau:

private Action<BusinessContract> _foo; 

public void Foo<T>(Action<T> bar) where T : BusinessContract 
{ 
    _foo = bar; 
} 

Trên đây thậm chí sẽ không biên dịch, mà làm tôi bối rối một chút. Tôi đang ràng buộc T là BusinessContract, vậy tại sao trình biên dịch không biết thanh đó có thể được gán cho _foo?

Trong cố gắng để làm được việc này, chúng tôi đã cố gắng thay đổi nó như sau:

public void Foo<T>(Action<T> bar) where T : BusinessContract 
{ 
    _foo = (Action<BusinessContract>)bar; 
} 

Bây giờ trình biên dịch là hạnh phúc, vì vậy tôi viết đoạn mã sau ở nơi khác trong ứng dụng của tôi:

Foo<Person>(p => p.Name = "Joe"); 

Và các ứng dụng thổi lên với một InvalidCastException tại thời gian chạy.

Tôi không hiểu. Tôi có nên đưa loại cụ thể hơn của mình vào loại ít cụ thể hơn và chỉ định không?

CẬP NHẬT

Jon đã trả lời câu hỏi để nhận được cái gật đầu cho rằng, nhưng chỉ để đóng vòng lặp về vấn đề này, dưới đây là cách chúng tôi đã kết thúc giải quyết vấn đề.

private Action<BusinessContract> _foo; 

public void Foo<T>(Action<T> bar) where T : BusinessContract 
{ 
    _foo = contract => bar((T)contract); 
} 

Tại sao chúng tôi làm việc này? Chúng tôi có một DAL Fake chúng tôi sử dụng để kiểm tra đơn vị. Với một trong các phương thức chúng ta cần cung cấp cho nhà phát triển thử nghiệm khả năng xác định phương thức nên làm khi nó được gọi trong quá trình thử nghiệm (đó là phương thức làm mới cập nhật một đối tượng được lưu trong bộ nhớ cache từ cơ sở dữ liệu). Mục đích của Foo là thiết lập những gì sẽ xảy ra khi refresh được gọi. IOW, ở những nơi khác trong lớp này chúng tôi có những điều sau đây.

public void Refresh(BusinessContract contract) 
{ 
    if(_foo != null) 
    { 
     _foo(contract); 
    } 
} 

Nhà phát triển thử nghiệm có thể, ví dụ, quyết định họ muốn đặt tên thành giá trị khác khi Làm mới được gọi.

Foo<Person>(p => p.Name = "New Name"); 

Trả lời

13

Bạn đã có hiệp phương sai và đối nghịch với sai đường tròn. Hãy xem xét Action<object>Action<string>. Loại bỏ các Generics thực tế, bạn đang cố gắng để làm một cái gì đó như thế này:

private Action<object> _foo; 

public void Foo(Action<string> bar) 
{ 
    // This won't compile... 
    _foo = bar; 
} 

Bây giờ giả sử sau đó chúng ta viết:

_foo(new Button()); 

Đó là tốt, vì Action<object> thể được thông qua bất kỳ đối tượng ... nhưng chúng tôi đã khởi tạo nó với một đại biểu mà phải lấy một đối số chuỗi. Ouch.

Đây không phải là loại an toàn, do đó, không biên dịch.

Một cách khác sẽ công việc mặc dù:

private Action<string> _foo; 

public void Foo(Action<object> bar) 
{ 
    // This is fine... 
    _foo = bar; 
} 

Bây giờ khi chúng tôi gọi _foo, chúng tôi để vượt qua trong một chuỗi - nhưng đó là tốt, bởi vì chúng tôi đã khởi tạo nó với một đại biểu mà có thể lấy bất kỳ tham chiếu object nào làm tham số, vì vậy, chúng tôi có thể cho nó một chuỗi.

Vì vậy, về cơ bản Action<T>contravariant - trong khi Func<T>hiệp biến:

Func<string> bar = ...; 
Func<object> foo = bar; // This is fine 
object x = foo(); // This is guaranteed to be okay 

Nó không rõ ràng những gì bạn đang cố gắng để làm với hành động, vì vậy tiếc là tôi không thể thực sự cung cấp cho bất kỳ lời khuyên nào về cách làm như thế này ...

+0

@MDeSchaepmeester: Không, tôi không ở trong mã đang hoạt động (nửa sau của bài đăng) Tôi chỉ định giá trị 'Action ' ('bar') cho biến' Action '(' _foo'), và sau đó gán giá trị 'Func ' ('bar)' vào biến 'Func ' ('foo'). –

+0

Bạn nói đúng, sai lầm ngớ ngẩn, tôi đã không thực sự đọc mã đó và cho rằng bạn đang gọi Foo với _foo như một tham số (Tôi đến đây vì tôi đang làm * cái đó *) – MarioDS

0

Không thể chỉ định vì bạn đang sử dụng một biến thể thay vì một biến thể, không có cách nào đảm bảo rằng loại chung có thể được gán cho foo.

+0

Vâng, nó không thực sự "vì [OP] đang sử dụng generics" - là vì 'Action ' là contravariant thay vì cov ariant. –

+0

@Jon Skeet, bạn nói đúng, tôi đã không giải thích tốt cho bản thân mình, tôi sẽ chỉnh sửa nó. –