2012-01-18 38 views
10

Tôi không rõ lý do tại sao đoạn mã sau đây không phải là covarient?Tại sao C# ra các thông số kiểu chung vi phạm hiệp phương sai?

public interface IResourceColl<out T> : IEnumerable<T> where T : IResource { 

    int Count { get; } 

    T this[int index] { get; } 

    bool TryGetValue(string SUID, out T obj); // Error here? 
    } 

Lỗi 1 đúng không hợp lệ: Các tham số kiểu 'T' phải invariantly hợp lệ về 'IResourceColl.TryGetValue (string, ra T)'. 'T' là covariant.

Giao diện của tôi chỉ sử dụng thông số mẫu ở vị trí đầu ra. Tôi có thể dễ dàng cấu trúc lại mã này vào một cái gì đó giống như

public interface IResourceColl<out T> : IEnumerable<T> where T : class, IResource { 

    int Count { get; } 

    T this[int index] { get; } 

    T TryGetValue(string SUID); // return null if not found 
    } 

nhưng tôi đang cố gắng để hiểu nếu mã ban đầu của tôi thực sự vi phạm hiệp phương sai hoặc nếu đây là một trình biên dịch hoặc .NET giới hạn của hiệp phương sai.

+0

có thể trùng lặp [C#: Tại sao không 'ref' và 'out' đa hình hỗ trợ?] (Http: // stackoverflow. com/questions/1207144/c-sharp-why-doesnt-ref-and-out-hỗ trợ-đa hình) – Jon

+0

Điều quan trọng cần lưu ý ở đây là ['out'] (http://msdn.microsoft.com/vi -us/library/ee332485.aspx) (bộ sửa đổi tham số) hoàn toàn không liên quan đến ['out'] (http://msdn.microsoft.com/en-us/library/dd469487.aspx) (được sử dụng trên một kiểu generic) tham số). – Jon

+0

@Jon - câu hỏi đó áp dụng cho C# 3.0 và trước đó. Cú pháp được mô tả ở đây là C# 4.0 – Oded

Trả lời

11

Vấn đề là thực sự ở đây:

bool TryGetValue(string SUID, out T obj); // Error here? 

Bạn đánh dấu obj như out tham số, đó vẫn có nghĩa rằng mặc dù bạn đang đi qua trongobj nên nó không thể là hiệp biến, vì bạn cả hai vượt qua trong một thể hiện của nhập T cũng như trả lại.

Edit:

Eric Lippert cho biết họ tốt hơn so với bất cứ ai tôi tham khảo his answer to "ref and out parameters in C# and cannot be marked as variant" và trích dẫn ông liên quan đến out thông số:

nó nên là hợp pháp để làm cho T đánh dấu là "out" ? Tiếc là không có. "out" thực sự không khác với "ref" đằng sau hậu trường. Sự khác biệt duy nhất giữa "out" và "ref" là trình biên dịch cấm đọc từ tham số ngoài trước khi được gán bởi callee và trình biên dịch yêu cầu gán trước khi callee trả về bình thường. Người nào đó đã viết triển khai giao diện này bằng ngôn ngữ .NET khác với C# sẽ có thể đọc từ mục trước khi nó được khởi tạo và do đó nó có thể được sử dụng làm đầu vào. Do đó, chúng tôi cấm đánh dấu T là "không tham gia" trong trường hợp này là. Thật đáng tiếc, nhưng không có gì chúng tôi có thể làm; chúng ta phải tuân thủ các quy tắc an toàn loại của CLR.

+0

Tôi có thể vượt qua trong một trường hợp của một cái gì đó khác hơn là một T, nhưng C# quy tắc yêu cầu tôi gán một cái gì đó trước khi tôi có thể đọc nó, vì vậy nó sẽ không bao giờ gây ra một vấn đề. – MerickOWA

+0

Ah tôi thấy bây giờ, nó có thể đọc giá trị trong các ngôn ngữ khác, và eric của blog chỉ ra các trường hợp ngay cả trong C# nơi nó 'có thể' phá vỡ. Điều đó giải thích tại sao hiệp phương sai bị vi phạm. – MerickOWA

+0

Vấn đề là cái gọi là tham số 'out' thực sự là không; một tham số 'out' thực sẽ làm cho trình biên dịch tạo ra một kiểu struct cho sự trả về của hàm, nó sẽ bao gồm kiểu trả về được chỉ định và tất cả các tham số' out'; người gọi sau đó sẽ tự động sao chép các trường thích hợp từ cấu trúc sang tham số 'out', và sau đó xem trường còn lại (nếu có) làm giá trị trả lại. Nếu các tham số 'out' được thực hiện theo cách đó, chúng thực sự có thể là biến thể. – supercat

1

Vi phạm hiệp phương sai vì giá trị được cung cấp cho tham số đầu ra phải là chính xác cùng loại với khai báo tham số đầu ra. Ví dụ, giả sử T là một chuỗi, hiệp phương sai có ngụ ý rằng nó sẽ là ok để làm

var someIResourceColl = new someIResourceCollClass<String>(); 
Object k; 
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String 
1

Kiểm tra ví dụ này chút và bạn sẽ hiểu tại sao nó không được phép:

public void Test() 
{ 
    string s = "Hello"; 
    Foo(out s); 
} 

public void Foo(out string s) //s is passed with "Hello" even if not usable 
{ 
    s = "Bye"; 
} 

out có nghĩa là s phải được gán chắc chắn trước khi thực thi rời khỏi phương thức và ngược lại bạn không thể sử dụng s cho đến khi nó được gán chắc chắn trong phần thân phương thức.Điều này có vẻ tương thích với hiệp phương sai quy tắc. Nhưng không có gì ngăn bạn chỉ định s tại trang cuộc gọi trước khi gọi phương thức. Giá trị này được chuyển đến phương pháp có nghĩa là ngay cả khi không phải là có thể sử dụng bạn đang chuyển một cách hiệu quả thông số của một loại được xác định sang phương pháp vi phạm quy tắc hiệp phương sai. được sử dụng làm kiểu trả về của một phương thức.

3

Đây là giải pháp thay thế có thể sử dụng phương pháp tiện ích mở rộng. Không nhất thiết phải thuận tiện từ điểm triển khai, nhưng người dùng phải hài lòng:

public interface IExample<out T> 
{ 
    T TryGetByName(string name, out bool success); 
} 

public static class HelperClass 
{ 
    public static bool TryGetByName<T>(this IExample<T> @this, string name, out T child) 
    { 
     bool success; 
     child = @this.TryGetByName(name, out success); 
     return success; 
    } 
} 

public interface IAnimal { }; 

public interface IFish : IAnimal { }; 

public class XavierTheFish : IFish { }; 

public class Aquarium : IExample<IFish> 
{ 
    public IFish TryGetByName(string name, out bool success) 
    { 
     if (name == "Xavier") 
     { 
      success = true; 
      return new XavierTheFish(); 
     } 
     else 
     { 
      success = false; 
      return null; 
     } 
    } 
} 

public static class Test 
{ 
    public static void Main() 
    { 
     var aquarium = new Aquarium(); 
     IAnimal child; 
     if (aquarium.TryGetByName("Xavier", out child)) 
     { 
      Console.WriteLine(child); 
     } 
    } 
} 
Các vấn đề liên quan