2012-01-02 26 views
109

Tôi có một danh sách private readonlyLinkLabel s (IList<LinkLabel>). Sau này tôi thêm LinkLabel s vào danh sách này và thêm những nhãn cho một FlowLayoutPanel thích sau:Chuyển đổi mảng biến thể từ x thành y có thể gây ra ngoại lệ thời gian chạy

foreach(var s in strings) 
{ 
    _list.Add(new LinkLabel{Text=s}); 
} 

flPanel.Controls.AddRange(_list.ToArray()); 

Resharper chỉ cho tôi một lời cảnh báo: Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation.

Xin hãy giúp tôi để tìm ra:

  1. gì này có nghĩa là?
  2. Đây là điều khiển người dùng và sẽ không được nhiều đối tượng thiết lập nhãn truy cập, để giữ mã như vậy sẽ không ảnh hưởng đến nó.

Trả lời

121

gì nó có nghĩa là thế này

Control[] controls = new LinkLabel[10]; // compile time legal 
controls[0] = new TextBox(); // compile time legal, runtime exception 

Và trong điều kiện tổng quát hơn

string[] array = new string[10]; 
object[] objs = array; // legal at compile time 
objs[0] = new Foo(); // again legal, with runtime exception 

Trong C#, bạn được phép tham khảo một mảng của các đối tượng (trong trường hợp của bạn, LinkLabels) như một mảng của một kiểu cơ sở (trong trường hợp này, là một mảng các điều khiển). Cũng là thời gian biên dịch hợp pháp để chỉ định một đối tượng khác là Control cho mảng đó. Vấn đề là mảng không thực sự là một mảng các điều khiển. Khi chạy, nó vẫn là một mảng của LinkLabels. Như vậy, nhiệm vụ, hoặc viết, sẽ ném một ngoại lệ.

+0

Tôi hiểu sự khác biệt về thời gian chạy/biên dịch như trong ví dụ của bạn nhưng không chuyển đổi từ loại đặc biệt sang loại cơ sở hợp pháp? Hơn nữa tôi đã gõ danh sách và tôi đang đi từ 'LinkLabel' (loại chuyên ngành) để' Control' (loại cơ sở). – TheVillageIdiot

+1

Có, việc chuyển đổi từ một LinkLabel thành Kiểm soát là hợp pháp, nhưng điều đó không giống như những gì đang xảy ra ở đây. Đây là cảnh báo về việc chuyển đổi từ một 'LinkLabel []' thành 'Control []', vẫn còn hợp pháp, nhưng có thể có một vấn đề thời gian chạy. Tất cả những gì đã thay đổi là cách mảng được tham chiếu. Bản thân mảng không bị thay đổi. Xem vấn đề? Mảng vẫn là một mảng của kiểu dẫn xuất. Tham chiếu là thông qua một mảng của kiểu cơ sở.Vì vậy, nó là thời gian biên dịch hợp pháp để gán một phần tử cho nó của kiểu cơ sở. Tuy nhiên, kiểu thời gian chạy sẽ không hỗ trợ nó. –

+0

Trong trường hợp của bạn, tôi không nghĩ rằng đó là một vấn đề, bạn chỉ đơn giản là sử dụng mảng để thêm vào một danh sách các điều khiển. –

8

Cảnh báo là do bạn về mặt lý thuyết có thể thêm Control ngoài LinkLabel vào số LinkLabel[] thông qua tham chiếu Control[]. Điều này sẽ gây ra một ngoại lệ thời gian chạy.

Chuyển đổi đang diễn ra ở đây vì AddRange mất Control[].

Nói chung, việc chuyển đổi vùng chứa của loại dẫn xuất sang vùng chứa của loại cơ sở chỉ an toàn nếu sau đó bạn không thể sửa đổi vùng chứa theo cách được phác thảo. Mảng không đáp ứng yêu cầu đó.

2

Với VS 2008, tôi không nhận được cảnh báo này. Điều này phải là mới đối với .NET 4.0.
Làm rõ: theo Sam Mackrill, đó là Resharper, người sẽ hiển thị cảnh báo.

Trình biên dịch C# không biết rằng AddRange sẽ không sửa đổi mảng được truyền cho nó. Vì AddRange có tham số kiểu Control[], theo lý thuyết, có thể cố gán một số TextBox vào mảng, sẽ hoàn toàn chính xác cho một mảng thực là Control, nhưng mảng đó thực tế là một mảng LinkLabels và sẽ không chấp nhận bài tập.

Đặt mảng đồng biến thể trong C# là một quyết định tồi của Microsoft. Mặc dù có thể là một ý tưởng tốt để có thể gán một mảng của một kiểu dẫn xuất cho một mảng của một kiểu cơ sở ở nơi đầu tiên, điều này có thể dẫn đến các lỗi thời gian chạy!

+1

Tôi nhận được cảnh báo này từ Resharper –

11

Tôi sẽ cố gắng làm rõ câu trả lời Anthony Pegram.

Generic loại là hiệp biến trên một số đối số kiểu khi nó trả về giá trị kiểu nói (ví dụ Func<out TResult> nhuận trường hợp của TResult, IEnumerable<out T> nhuận trường hợp của T). Tức là, nếu một cái gì đó trả về các thể hiện của TDerived, bạn cũng có thể làm việc với các trường hợp như thể chúng là TBase.

Loại chung là contravariant trên một số đối số loại khi nó chấp nhận các giá trị của loại đã nói (ví dụ: Action<in TArgument> chấp nhận các trường hợp TArgument). Tức là, nếu cần một số phiên bản của TBase, bạn cũng có thể vượt qua trong các phiên bản TDerived. Có vẻ như khá hợp lý rằng các loại chung mà cả hai đều chấp nhận và trả về các trường hợp của một số loại (trừ khi được xác định hai lần trong chữ ký loại chung, ví dụ: CoolList<TIn, TOut>) không phải là biến thể hay contravariant trên đối số kiểu tương ứng. Ví dụ: List được định nghĩa trong .NET 4 là List<T>, không phải List<in T> hoặc List<out T>.

Một số lý do tương thích có thể đã khiến Microsoft bỏ qua đối số đó và làm cho mảng covariant trên đối số loại giá trị của chúng. Có thể họ đã tiến hành phân tích và thấy rằng hầu hết mọi người chỉ sử dụng mảng như thể họ chỉ đọc (nghĩa là họ chỉ sử dụng công cụ khởi tạo mảng để ghi dữ liệu vào mảng), và, như vậy, lợi thế vượt quá những bất lợi do thời gian chạy có thể xảy ra lỗi khi ai đó cố gắng sử dụng hiệp phương sai khi viết vào mảng. Do đó nó được cho phép nhưng không được khuyến khích.

Đối với câu hỏi ban đầu của bạn, tạo ra một list.ToArray() mới LinkLabel[] với các giá trị sao chép từ danh sách ban đầu, và, để thoát khỏi cảnh báo (hợp lý), bạn sẽ cần phải vượt qua trong Control[] để AddRange. list.ToArray<Control>() sẽ thực hiện công việc: ToArray<TSource> chấp nhận IEnumerable<TSource> làm đối số và trả về TSource[]; List<LinkLabel> thực hiện chỉ đọc IEnumerable<out LinkLabel>, trong đó, nhờ vào IEnumerable hiệp phương sai, có thể được chuyển đến phương thức chấp nhận IEnumerable<Control> làm đối số của nó.

6

Các thẳng nhất về phía trước "giải pháp"

flPanel.Controls.AddRange(_list.AsEnumerable());

Bây giờ kể từ khi bạn đang covariantly thay đổi List<LinkLabel> để IEnumerable<Control> không có nhiều mối quan tâm vì nó không thể "add" một mục để một đếm được.

5

nguyên nhân gốc rễ Vấn đề được mô tả một cách chính xác trong câu trả lời khác, nhưng để giải quyết cảnh báo, bạn luôn có thể viết:

_list.ForEach(lnkLbl => flPanel.Controls.Add(lnkLbl)); 
+1

Một giải pháp thiết thực tốt! –

1

Làm thế nào về điều này?

flPanel.Controls.AddRange(_list.OfType<Control>().ToArray()); 
+0

Kết quả tương tự như '_list.ToArray ()'. – jsuddsjr

Các vấn đề liên quan