[C#] x에서 y로 공변량 배열을 변환하면 런타임 예외가 발생할 수 있습니다

나는이 private readonly목록 LinkLabel들 ( IList<LinkLabel>). 나중에이 LinkLabel목록 에 s를 추가하고 해당 레이블을 FlowLayoutPanel다음과 같이 추가 합니다.

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

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

Resharper는 다음과 같은 경고를 표시 Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation합니다..

알아낼 수 있도록 도와주세요.

  1. 이것은 무엇을 의미합니까?
  2. 이것은 사용자 컨트롤이며 여러 개체가 레이블을 설정하기 위해 액세스하지 않으므로 코드를 그대로 유지해도 영향을 미치지 않습니다.


답변

이것이 의미하는 것은

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

그리고 더 일반적인 용어로

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

C #에서는 개체 배열 (이 경우 LinkLabels)을 기본 형식의 배열 (이 경우 Controls 배열)로 참조 할 수 있습니다. 또한 배열에 다른 객체 를 할당 하는 것이 합법적 인 컴파일 시간 Control입니다. 문제는 배열 이 실제로 컨트롤 배열 이 아니라는 것입니다. 런타임에 여전히 LinkLabels의 배열입니다. 따라서 할당 또는 쓰기에서 예외가 발생합니다.


답변

Anthony Pegram의 답변을 명확히하려고 노력할 것입니다.

그것은 말했다 유형의 값 (예를 들어, 반환 할 때 일반 유형은 어떤 종류의 인수에 공변 인 Func<out TResult>의 반환 경우 TResult, IEnumerable<out T>수익률 인스턴스 T). 즉, 무언가가의 인스턴스를 반환하면 마치 인스턴스 인 TDerived것처럼 작업 할 수 있습니다 TBase.

제네릭 형식은 해당 형식의 값을 수락 할 때 (예 :의 Action<in TArgument>인스턴스를 수락 할 때) 일부 형식 인수에 대해 반 변형 적입니다 TArgument. 즉, 무언가의 인스턴스가 필요한 경우의 인스턴스를 TBase전달할 수도 있습니다 TDerived.

(예를 들어 일반 형식 서명에서 두 번 정의되지 않은 한) 일부 형식의 인스턴스를 허용하고 반환하는 일반 형식 CoolList<TIn, TOut>은 해당 형식 인수에 대해 공변량 또는 반 변형이 아닙니다. 예를 들어, List.NET 4 List<T>에서는 List<in T>또는 로 지정 되지 않습니다 List<out T>.

호환성 문제로 인해 Microsoft는 해당 인수를 무시하고 값 유형 인수에 대해 배열을 공 변형으로 만들었습니다. 어쩌면 그들은 분석을 수행하여 대부분의 사람들이 읽기 전용 인 것처럼 배열을 사용한다는 것을 알았습니다 (즉, 배열 초기화 장치를 사용하여 배열에 일부 데이터를 쓰는 경우). 따라서 장점은 런타임으로 인한 단점을 능가합니다. 누군가 배열에 쓸 때 공분산을 사용하려고 할 때 오류가 발생합니다. 따라서 허용되지만 권장되지는 않습니다.

원래 질문에 대해서는 원래 목록에서 복사 된 값 list.ToArray()으로 새로운 것을 만들고 LinkLabel[](합리적) 경고를 없애려면에 전달해야 Control[]합니다 AddRange. list.ToArray<Control>()작업을 수행합니다 : 인수로 ToArray<TSource>받아들이고 IEnumerable<TSource>리턴 TSource[]; 공분산 덕분에 인수로 받아들이는 메소드에 전달 될 수있는 List<LinkLabel>읽기 전용을 구현 합니다.IEnumerable<out LinkLabel>IEnumerableIEnumerable<Control>


답변

가장 직접적인 “솔루션”

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

이제 공변 적으로 변경 List<LinkLabel>하기 IEnumerable<Control>때문에 항목을 열거 가능 항목에 “추가”할 수 없으므로 더 이상 걱정할 필요가 없습니다.


답변

경고 때문에 당신이 이론적으로 추가 할 수 있다는 사실이다 Control(A)보다 기타를 LinkLabel받는 LinkLabel[]관통 Control[]그것을 참조. 이로 인해 런타임 예외가 발생합니다.

변환 때문에 여기에 무슨 일이 일어나고 AddRange합니다 Control[].

보다 일반적으로 파생 된 유형의 컨테이너를 기본 유형의 컨테이너로 변환하는 것은 나중에 간략하게 설명 된 방식으로 컨테이너를 수정할 수없는 경우에만 안전합니다. 어레이는 해당 요구 사항을 충족하지 않습니다.


답변

문제의 근본 원인은 다른 답변에 올바르게 설명되어 있지만 경고를 해결하기 위해 항상 다음과 같이 쓸 수 있습니다.

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


답변

VS 2008에서는이 경고가 표시되지 않습니다. .NET 4.0에 새로운 것이어야합니다.
설명 : Sam Mackrill에 따르면 경고를 표시하는 사람은 Resharper입니다.

C # 컴파일러는 AddRange전달 된 배열을 수정 하지 않습니다 . AddRange유형의 매개 변수가 있기 때문에 Control[]이론적 TextBox으로는 배열에 a를 할당하려고 시도 할 수 있습니다.이 배열은의 실제 배열에 완벽하게 맞지만 Control배열은 실제로 배열이며 LinkLabels이러한 할당을 수락하지 않습니다.

C #에서 배열을 공변량으로 만드는 것은 Microsoft의 잘못된 결정이었습니다. 파생 형식의 배열을 기본 형식의 배열에 처음 할당하는 것이 좋은 생각이지만 런타임 오류가 발생할 수 있습니다!


답변

이건 어때요?

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