Chuyển đổi mảng đồng biến từ x sang y có thể gây ra ngoại lệ thời gian chạy


142

Tôi có một private readonlydanh sách LinkLabels ( IList<LinkLabel>). Sau này tôi thêm LinkLabels vào danh sách này và thêm các nhãn đó vào FlowLayoutPanelnhư sau:

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

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

Resharper cho tôi thấy một 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. Những gì hiện phương tiện này?
  2. Đây là một điều khiển người dùng và sẽ không được nhiều đối tượng truy cập để thiết lập nhãn, vì vậy việc giữ mã như vậy sẽ không ảnh hưởng đến nó.

Câu trả lời:


154

Nó có nghĩa là gì

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

Và nói chung chung

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 chiếu một mảng các đối tượng (trong trường hợp của bạn, LinkLabels) dưới dạng một mảng của loại cơ sở (trong trường hợp này là một mảng Điều khiển). Nó cũng được biên dịch hợp pháp thời gian để gán một đối tượng khác là một Controlmảng. Vấn đề là mảng thực sự không phải là một mảng Điều khiển. Trong thời gian chạy, nó vẫn là một mảng của LinkLabels. Như vậy, bài tập, hoặc viết, sẽ ném một ngoại lệ.


Tôi hiểu thời gian chạy / biên dịch chênh lệch thời gian 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? Ngoài ra, tôi đã gõ danh sách và tôi đang đi từ LinkLabel(loại chuyên biệt) đến Control(loại cơ sở).
TheVillageIdiot

2
Có, chuyển đổi từ LinkLabel sang Control là hợp pháp, nhưng điều đó không giống với những gì đang xảy ra ở đây. Đây là cảnh báo về việc chuyển đổi từ một LinkLabel[]sang Control[], vẫn còn hợp pháp, nhưng có thể có vấn đề về thời gian chạy. Tất cả những gì đã thay đổi là cách mảng đang được tham chiếu. Các mảng không 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 loại cơ sở. Do đó, việc biên dịch một phần tử cho loại cơ sở là hợp pháp. Tuy nhiên, loại thời gian chạy sẽ không hỗ trợ nó.
Anthony Pegram

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

6
Nếu bất cứ ai thắc mắc tại sao các mảng bị sai sai trong C # thì đây là lời giải thích của Eric Lippert : Nó đã được thêm vào CLR vì Java yêu cầu nó và các nhà thiết kế CLR muốn có thể hỗ trợ các ngôn ngữ giống Java. Sau đó chúng tôi đã thêm và thêm nó vào C # vì nó nằm trong CLR. Quyết định này đã gây tranh cãi vào thời điểm đó và tôi không hài lòng lắm về điều đó, nhưng hiện tại chúng tôi không thể làm gì được.
franssu

14

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

Kiểu chung là covariant trên một số đối số kiểu khi nó trả về các giá trị của kiểu đã nói (ví dụ Func<out TResult>trả về các thể hiện của TResult, IEnumerable<out T>trả về các thể hiện của T). Đó 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 thể hiện như thể chúng là của TBase.

Loại chung là chống chỉ định đối với 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 thể hiện của TArgument). Đó là, nếu một cái gì đó cần phiên bản của TBase, bạn cũng có thể vượt qua trong trường hợp TDerived.

Có vẻ khá hợp lý khi các loại chung chung chấp nhận và trả về các thể hiện của một số loại (trừ khi nó đượ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 đổi cũng như chống đối với đối số loại tương ứng. Ví dụ, Listđược định nghĩa trong .NET 4 là List<T>, không 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à tạo ra các mảng đồng biến 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 các mảng như thể họ chỉ đọc (nghĩa là họ chỉ sử dụng các công cụ khởi tạo mảng để ghi một số dữ liệu vào một mảng), và như vậy, những ưu điểm vượt trội hơn những nhược điểm do thời gian chạy có thể gây ra lỗi khi ai đó sẽ 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, hãy list.ToArray()tạo một câu hỏi mới LinkLabel[]với các giá trị được sao chép từ danh sách ban đầu và để loại bỏ cảnh báo (hợp lý), bạn sẽ cần chuyển Control[]đến 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>, nhờ IEnumerablehiệp phương sai, có thể được truyền cho phương thức chấp nhận IEnumerable<Control>làm đối số của nó.


11

"Giải pháp" thẳng nhất

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

Bây giờ vì bạn đang thay đổi một cách ngẫu nhiên List<LinkLabel>thành IEnumerable<Control>không có mối quan tâm nào nữa vì không thể "thêm" một mục vào một liệt kê.


10

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

Việc chuyển đổi đang diễn ra ở đây vì AddRangemất một Control[].

Tổng quát hơn, chuyển đổi một loại vật chứa có nguồn gốc sang loại vật chứa loại cơ sở chỉ an toàn nếu sau đó bạn không thể sửa đổi vật chứa theo cách vừa được phác thảo. Mảng không thỏa mãn yêu cầu đó.


5

Nguyên nhân gốc rễ của vấn đề được mô tả chính xác trong các 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));

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 hiển thị cảnh báo.

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

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


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

1

Còn cái này thì sao?

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

2
Kết quả tương tự như _list.ToArray<Control>().
jsuddsjr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.