Mam private readonly
listę LinkLabel
s ( IList<LinkLabel>
). Później dodaję LinkLabel
s do tej listy i dodaję te etykiety do FlowLayoutPanel
następujących:
foreach(var s in strings)
{
_list.Add(new LinkLabel{Text=s});
}
flPanel.Controls.AddRange(_list.ToArray());
ReSharper pokazuje mi ostrzeżenie: Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation
.
Proszę, pomóż mi zrozumieć:
- Co to znaczy?
- Jest to kontrolka użytkownika i wiele obiektów nie będzie do niej używać w celu skonfigurowania etykiet, więc zachowanie kodu jako takiego nie ma na nią wpływu.
źródło
LinkLabel
(typ specjalistyczny) doControl
(typ podstawowy).LinkLabel[]
doControl[]
, która nadal jest legalna, ale może powodować problem w czasie wykonywania. Wszystko, co się zmieniło, to sposób, w jaki odwołuje się do tablicy. Sama tablica nie jest zmieniana. Widzisz problem? Tablica jest nadal tablicą typu pochodnego. Odwołanie odbywa się za pośrednictwem tablicy typu podstawowego. Dlatego dozwolony jest czas kompilacji, aby przypisać do niego element typu podstawowego. Jednak typ środowiska wykonawczego tego nie obsługuje.Spróbuję wyjaśnić odpowiedź Anthony'ego Pegrama.
Typ ogólny jest kowariantny dla argumentu typu, gdy zwraca wartości tego typu (np.
Func<out TResult>
Zwraca wystąpieniaTResult
,IEnumerable<out T>
zwraca wystąpieniaT
). Oznacza to, że jeśli coś zwraca wystąpienia programuTDerived
, możesz równie dobrze pracować z takimi wystąpieniami, jakby byłyTBase
.Typ ogólny jest kontrawariantny w przypadku niektórych argumentów typu, gdy akceptuje wartości tego typu (np.
Action<in TArgument>
Akceptuje wystąpieniaTArgument
). Oznacza to, że jeśli coś wymaga instancjiTBase
, możesz równie dobrze przejść w instancjachTDerived
.Wydaje się całkiem logiczne, że typy generyczne, które zarówno akceptują, jak i zwracają instancje pewnego typu (chyba że jest to zdefiniowane dwukrotnie w sygnaturze typu ogólnego, np.
CoolList<TIn, TOut>
) Nie są kowariantne ani kontrawariantne w odpowiednim argumencie typu. Na przykładList
jest zdefiniowany w .NET 4 jakoList<T>
, nieList<in T>
lubList<out T>
.Niektóre przyczyny zgodności mogły spowodować, że firma Microsoft zignoruje ten argument i sprawi, że tablice będą kowariantne w ich argumencie typu wartości. Być może przeprowadzili analizę i odkryli, że większość ludzi używa tablic tylko tak, jakby były tylko do odczytu (to znaczy używają inicjatorów tablic tylko do zapisania niektórych danych w tablicy) i jako takie, zalety przeważają nad wadami spowodowanymi przez możliwe środowisko uruchomieniowe błędy, gdy ktoś spróbuje skorzystać z kowariancji podczas zapisu do tablicy. Dlatego jest to dozwolone, ale nie zalecane.
Jeśli chodzi o Twoje oryginalne pytanie,
list.ToArray()
tworzy noweLinkLabel[]
z wartościami skopiowanymi z oryginalnej listy i aby pozbyć się (rozsądnego) ostrzeżenia, musisz przejśćControl[]
doAddRange
.list.ToArray<Control>()
wykona zadanie:ToArray<TSource>
przyjmujeIEnumerable<TSource>
jako argument i zwracaTSource[]
;List<LinkLabel>
implementuje tylko do odczytuIEnumerable<out LinkLabel>
, co dziękiIEnumerable
kowariancji mogłoby zostać przekazane do metody akceptującejIEnumerable<Control>
jako swój argument.źródło
Najprostsze „rozwiązanie”
flPanel.Controls.AddRange(_list.AsEnumerable());
Odkąd zmieniasz się kowariantnie
List<LinkLabel>
na,IEnumerable<Control>
nie ma już obaw, ponieważ nie jest możliwe „dodanie” pozycji do wyliczenia.źródło
Ostrzeżenie wynika z faktu, że teoretycznie można dodać
Control
inny niż aLinkLabel
doLinkLabel[]
poprzezControl[]
odniesienie do niego. Spowodowałoby to wyjątek w czasie wykonywania.Konwersja ma miejsce tutaj, ponieważ
AddRange
trwaControl[]
.Mówiąc bardziej ogólnie, konwersja kontenera typu pochodnego na kontener typu podstawowego jest bezpieczna tylko wtedy, gdy nie można później zmodyfikować kontenera w sposób właśnie przedstawiony. Tablice nie spełniają tego wymagania.
źródło
Główna przyczyna problemu została poprawnie opisana w innych odpowiedziach, ale aby rozwiązać problem, zawsze możesz napisać:
źródło
W VS 2008 nie otrzymuję tego ostrzeżenia. To musi być nowość w .NET 4.0.
Wyjaśnienie: według Sama Mackrilla to Resharper wyświetla ostrzeżenie.
Kompilator C # nie wie, że
AddRange
nie zmodyfikuje przekazanej do niego tablicy. PonieważAddRange
ma parametr typuControl[]
, teoretycznie mógłby próbować przypisać aTextBox
do tablicy, co byłoby całkowicie poprawne dla prawdziwej tablicyControl
, ale tablica jest w rzeczywistości tablicąLinkLabels
i nie zaakceptuje takiego przypisania.Tworzenie kowariancji tablic w języku C # było złą decyzją Microsoftu. Chociaż możliwość przypisania tablicy typu pochodnego do tablicy typu podstawowego może wydawać się dobrym pomysłem, może to prowadzić do błędów w czasie wykonywania!
źródło
Co powiesz na to?
źródło
_list.ToArray<Control>()
.