Чому в C # неможливо зберегти об'єкт List <string> у змінній List <object>


85

Здається, що об'єкт List не може зберігатися у змінній List у C # і навіть не може бути явно переданий таким чином.

List<string> sl = new List<string>();
List<object> ol;
ol = sl;

результати в Неможливо неявно перетворити тип System.Collections.Generic.List<string>наSystem.Collections.Generic.List<object>

І потім...

List<string> sl = new List<string>();
List<object> ol;
ol = (List<object>)sl;

результати в Не вдається перетворити тип System.Collections.Generic.List<string>наSystem.Collections.Generic.List<object>

Звичайно, ви можете зробити це, витягнувши все зі списку рядків і повернувши його по черзі, але це досить заплутане рішення.


5
Це зміниться із C # 4.0, тож ви можете шукати коваріацію та контраваріацію. Це дозволить робити такі речі безпечним способом.
Джон Лейдегрен,

Більш-менш дублікат: stackoverflow.com/questions/317335/…
Джоел в Ге

7
Джон> C # 4 не дозволить цього. Подумайте про ol.Add (new object ());
Гійом

Він відповів Джон тарілочках тут: stackoverflow.com/a/2033921/1070906
JCH2k

Відповіді:


39

Подумайте про це так, якщо вам потрібно було виконати такий приклад, а потім додати об'єкт типу Foo до списку, список рядків більше не буде послідовним. Якби вам було повторити перше посилання, ви отримали б виняток класу класу, оскільки як тільки ви потрапили до екземпляра Foo, Foo не вдалося перетворити на рядок!

Як допоміжна нотатка, я думаю, було б більш значущим, чи можете ви зробити зворотний привід:

List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol;

Я давно не використовував C #, тому я не знаю, чи це законно, але такий тип акторського складу фактично (потенційно) корисний. У цьому випадку ви переходите від більш загального класу (об’єкта) до більш конкретного класу (рядка), який розширюється від загального. Таким чином, якщо ви додаєте до списку рядків, ви не порушуєте список об'єктів.

Хтось знає чи може перевірити, чи є такий акторський склад законним у C #?


4
Я думаю, що ваш приклад страждає від тієї ж проблеми. Що станеться, якщо в ol є щось, що не є рядком? Проблема в тому, що деякі методи зі Списку працювали б нормально, наприклад, додавання / вставлення. Але ітерація може бути справжньою проблемою.
Кріс Аммерман,

3
Ерік Ліпперт має велику серію публікацій у блозі на цю тему: чому це може допомогти додати ковариантність і противаріантність протилежностей до загальних методів, але може ніколи не працювати так, як ми хотіли б на рівні класу. is.gd/3kQc
Кріс

@ChrisAmmerman: Якби olв ньому було щось, що не є рядком, я гадаю, я би очікував, що акторський склад не вдасться виконати. Але якщо ви справді зіткнетеся з проблемами, це якщо акторський склад вдався, а потім до них було додано щось, olщо не є ланцюжком. Оскільки slпосилання на один і той же об’єкт, тепер ваш List<string>би містив нерядок. Проблема Addполягає в тому, що, на мою думку, виправдовує те, чому цей код не компілюється, але він компілюється, якщо ви перейдете List<object> olна IEnumerable<object> ol, який не має Add. (Я перевірив це в C # 4.)
Тім Гудман

З цією зміною він компілює, але видає, InvalidCastExceptionоскільки тип виконання olвсе ще є List<object>.
Тім Гудман

36

Якщо ви використовуєте .NET 3.5, подивіться на метод Enumerable.Cast. Це метод розширення, тому ви можете викликати його безпосередньо у списку.

List<string> sl = new List<string>();
IEnumerable<object> ol;
ol = sl.Cast<object>();

Це не зовсім те, про що ви просили, але слід зробити трюк.

Редагувати: Як зазначає Zooba, ви можете зателефонувати ol.ToList (), щоб отримати Список


14

Ви не можете здійснювати трансляцію між загальними типами з різними параметрами типу. Спеціалізовані загальні типи не є частиною одного дерева успадкування, а отже і не пов’язані типи.

Для цього pre-NET 3.5:

List<string> sl = new List<string>();
// Add strings to sl

List<object> ol = new List<object>();

foreach(string s in sl)
{
    ol.Add((object)s);  // The cast is performed implicitly even if omitted
}

Використання Linq:

var sl = new List<string>();
// Add strings to sl

var ol = new List<object>(sl.Cast<object>());

// OR
var ol = sl.Cast<object>().ToList();

// OR (note that the cast to object here is required)
var ol = sl.Select(s => (object)s).ToList();

11

Причина в тому, що такий загальний клас, як List<>правило, для більшості цілей розглядається зовні як звичайний клас. наприклад, коли ви говорите, List<string>()що компілятор говорить ListString()(що містить рядки). [Технічні люди: це надзвичайно зрозуміла англійською мовою версія того, що відбувається]

Отже, очевидно, що компілятор не може бути достатньо розумним, щоб перетворити ListString в ListObject, додавши елементи своєї внутрішньої колекції.

Ось чому для IEnumerable існують методи розширення, такі як Convert (), які дозволяють легко забезпечити перетворення елементів, що зберігаються в колекції, які можуть бути настільки простими, як перекидання з одного на інший.


6

Це має багато спільного з коваріацією, наприклад, загальні типи розглядаються як параметри, і якщо параметри не визначаються належним чином до більш конкретного типу, то операція не вдається. Наслідком такого є те, що ви насправді не можете привести до більш загального типу типу об'єкта. І як заявив Рекс, об’єкт List не перетворить кожен об’єкт замість вас.

Ви можете замість цього спробувати код ff:

List<string> sl = new List<string>();
//populate sl
List<object> ol = new List<object>(sl);

або:

List<object> ol = new List<object>();
ol.AddRange(sl);

ol (теоретично) без проблем скопіює весь вміст sl.





1

Це насправді для того, щоб ви не намагалися вставити будь-який непарний "об'єкт" у своєму варіанті списку "ol" (як List<object>, здавалося б, дозволяють) - тому що тоді ваш код зазнає аварії (оскільки список насправді є List<string>і буде приймати лише об'єкти типу String) ). Ось чому ви не можете передати свою змінну в більш загальну специфікацію.

На Java все навпаки, у вас немає дженериків, і замість цього все є "Список об'єктів" під час виконання, і ви дійсно можете вкласти будь-який дивний об'єкт у ваш нібито строго набраний Список. Шукайте "Reified generics", щоб побачити більш широке обговорення проблеми Java ...


1

Така коваріація щодо загальних препаратів не підтримується, але насправді це можна зробити за допомогою масивів:

object[] a = new string[] {"spam", "eggs"};

C # виконує перевірки виконання, щоб не дати вам, скажімо, встановити int в a.


0

Ось ще одне рішення pre.NET 3.5 для будь-якого IList, вміст якого можна неявно передавати.

public IList<B> ConvertIList<D, B>(IList<D> list) where D : B
{
    List<B> newList = new List<B>();

    foreach (D item in list)
    {
        newList.Add(item);
    }

    return newList;
}

(На основі прикладу Zooba)


0

Я маю:

private List<Leerling> Leerlingen = new List<Leerling>();

І я збирався заповнити його даними, зібраними у тому, List<object> що, нарешті, для мене спрацювало ось що:

Leerlingen = (List<Leerling>)_DeserialiseerLeerlingen._TeSerialiserenObjecten.Cast<Leerling>();

.Castйого до типу, який ви хочете отримати IEnumerableвід цього типу, а потім введіть IEnemuerableдо List<>потрібного.


0

Мм, завдяки попереднім коментарям я знайшов два способи це дізнатись. Перший - це отримання рядкового списку елементів, а потім перекидання його до списку об’єктів IEnumerable:

IEnumerable<object> ob;
List<string> st = new List<string>();
ob = st.Cast<object>();

А другий - уникати типу об'єкта IEnumerable, просто перекинути рядок до типу об'єкта, а потім використовувати функцію "toList ()" у тому ж реченні:

List<string> st = new List<string>();
List<object> ob = st.Cast<object>().ToList();

Мені більше подобається другий спосіб. Сподіваюся, це допоможе.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.