Как инициализировать список с экземплярами производного типа [duplicate]

Для меня самый лучший способ его отображения (нижний колонтитул) прилипает к дну, но не покрывает контент все время:

  #my_footer {position: static fixed;  bottom: 0}  
135
задан Bedasso 17 November 2014 в 22:13
поделиться

9 ответов

Способ выполнения этой работы состоит в том, чтобы перебирать список и отбрасывать элементы. Это можно сделать с помощью ConvertAll:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

Вы также можете использовать Linq:

List<A> listOfA = new List<C>().Cast<A>().ToList();
177
ответ дан Mark Byers 17 August 2018 в 09:16
поделиться
  • 1
    другой вариант: List & lt; A & gt; listOfA = listOfC.ConvertAll (x = & gt; (A) x); – ahaliav fox 16 July 2014 в 08:56
  • 2
    Какой из них быстрее? ConvertAll или Cast? – modiX 3 September 2014 в 11:21
  • 3
    Это создаст копию списка. Если вы добавите или удалите что-то в новом списке, это не будет отражено в исходном списке. А во-вторых, существует большая производительность и ограничение памяти, так как он создает новый список с существующими объектами. См. Мой ответ ниже для решения без этих проблем. – Bigjim 5 June 2015 в 09:07
  • 4
    Ответ на modiX: ConvertAll - это метод в List & lt; T & gt; , поэтому он будет работать только в общем списке; он не будет работать с IEnumerable или IEnumerable & lt; T & gt ;; ConvertAll также может выполнять пользовательские преобразования, а не только литье, например. ConvertAll (дюймы = & gt; дюймы * 25,4). Литой & Lt; A & GT; является методом расширения LINQ, поэтому работает на любом IEnumerable & lt; T & gt; (а также работает для неосновного IEnumerable), и, как и большинство LINQ, он использует отложенное выполнение, то есть только преобразует столько элементов, сколько извлекается. Подробнее об этом читайте здесь: codeblog.jonskeet.uk/2011/01/13/… – Edward 13 July 2018 в 14:36

Процитировать великое объяснение Эрика

Что происходит? Вы хотите, чтобы список жирафов содержал тигра? Вы хотите сбой? или вы хотите, чтобы компилятор защитил вас от сбоя, сделав это незаконным в первую очередь? Мы выбираем последний.

Но что, если вы хотите выбрать для сбоя во время выполнения вместо ошибки компиляции? Обычно вы используете Cast & lt;> или ConvertAll & lt;>, но тогда у вас будет две проблемы: она создаст копию списка. Если вы добавите или удалите что-то в новом списке, это не будет отражено в исходном списке. Во-вторых, существует большая производительность и ограничение памяти, так как он создает новый список с существующими объектами.

У меня была такая же проблема, и поэтому я создал класс-оболочку, который может использовать общий список без создания полностью новый список.

В исходном вопросе вы могли бы использовать:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

и здесь класс-оболочка (+ метод расширения CastList для удобства использования)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}
46
ответ дан Bigjim 17 August 2018 в 09:16
поделиться
  • 1
    Теперь это БРИЛЛИАНТНЫЙ ответ - я не могу понять, что у этого нет всех голосов! – Paul Coldrey 29 January 2016 в 07:12
  • 2
    Я просто использовал его как модель просмотра MVC и получил прекрасный универсальный бритвенный фрагмент. Удивительная идея! Я так счастлив, что прочитал это. – Stefan Cebulak 22 October 2016 в 21:14
  • 3
    Я бы добавил только «где TTo: TFrom» в объявлении класса, поэтому компилятор может предупреждать о некорректном использовании. Создание CastedList несвязанных типов в любом случае бессмысленно и создание «CastedList & lt; TBase, TDerived & gt;» & quot; было бы бесполезно: вы не можете добавлять к нему обычные объекты TBase, и любой TDerived, который вы получаете из исходного списка, уже может использоваться в качестве TBase. – Wolfzoon 2 November 2016 в 17:28
  • 4
    @PaulColdrey Увы, шестилетний главный старт виноват. – Wolfzoon 2 November 2016 в 17:31
  • 5
    @Wolfzoon: стандартный .Cast & lt; T & gt; () также не имеет этого ограничения. Я хотел сделать так же, как и .Cast, поэтому можно привести список животных в список тигров и получить исключение, если оно будет содержать Жираф, например. Так же, как это было бы с Cast ... – Bigjim 12 April 2017 в 11:13

Насколько он не работает, может быть полезно понять ковариацию и контравариантность .

Просто чтобы показать, почему этот не должен , это изменение кода, который вы указали:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

Должно ли это работать? Первый элемент в списке относится к типу «B», но тип элемента DerivedList - C.

Теперь предположим, что мы действительно просто хотим создать общую функцию, которая работает в списке некоторых тип, который реализует A, но нам все равно, какой тип:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}
22
ответ дан Chris Pitman 17 August 2018 в 09:16
поделиться
  • 1
    & quot; Должна ли эта работа & quot; - Я бы согласился и нет. Я не вижу причин, по которым вы не сможете написать код и не скомпилировать его во время компиляции, поскольку на самом деле он делает неверное преобразование во время доступа к FirstItem как type 'C'. Существует много аналогичных способов взорвать C #, которые поддерживаются. IF , вы действительно хотите достичь этой функциональности по уважительной причине (и их много), тогда ответ Bigjim ниже выглядит потрясающе. – Paul Coldrey 29 January 2016 в 07:19

Это расширение для блестящего ответа BigJim .

В моем случае у меня был класс NodeBase со словарем Children, и мне нужен был способ в целом сделать O (1) поиск от детей. Я пытался вернуть частное поле слова в getter из Children, поэтому, очевидно, я хотел избежать дорогостоящего копирования / итерации. Поэтому я использовал код Bigjim, чтобы передать Dictionary<whatever specific type> в общий Dictionary<NodeBase>:

// Abstract parent class
public abstract class NodeBase
{
    public abstract IDictionary<string, NodeBase> Children { get; }
    ...
}

// Implementing child class
public class RealNode : NodeBase
{
    private Dictionary<string, RealNode> containedNodes;

    public override IDictionary<string, NodeBase> Children
    {
        // Using a modification of Bigjim's code to cast the Dictionary:
        return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
    }
    ...
}

. Это сработало хорошо. Тем не менее, я в конечном итоге столкнулся с несвязанными ограничениями и в итоге создал абстрактный метод FindChild() в базовом классе, который вместо этого выполнял бы поиск. Как оказалось, это устранило потребность в литом словаре в первую очередь. (Я смог заменить его простым IEnumerable для моих целей.)

Итак, вопрос, который вы можете задать (особенно если производительность - это проблема, запрещающая вам использовать .Cast<> или .ConvertAll<>), is:

«Нужно ли мне вообще использовать всю коллекцию, или я могу использовать абстрактный метод для хранения специальных знаний, необходимых для выполнения задачи, и тем самым избежать прямого доступа к коллекции?»

Иногда самое простое решение - лучшее.

0
ответ дан Community 17 August 2018 в 09:16
поделиться

Прежде всего, прекратите использовать непонятные для понимания имена классов, такие как A, B, C. Используйте животных, млекопитающих, жирафов или продуктов питания, фруктов, апельсинов или что-то там, где отношения понятны.

Тогда возникает вопрос: «Почему я не могу назначить список жирафов переменной типа животного, так как я могу назначить жирафа переменной типа животного?»

Ответ: предположим ты мог. Что может тогда пойти не так?

Ну, вы можете добавить Тигра в список животных. Предположим, мы разрешаем вам помещать список жирафов в переменную, содержащую список животных. Затем вы пытаетесь добавить тигра в этот список. Что происходит? Вы хотите, чтобы список жирафов содержал тигра? Вы хотите сбой? или вы хотите, чтобы компилятор защитил вас от сбоя, сделав это недопустимым в первую очередь?

Мы выбираем последний.

Этот вид преобразования называется «ковариантным «преобразование. В C # 4 мы разрешим вам делать ковариантные преобразования на интерфейсах и делегатах , когда известно, что преобразование всегда безопасно . Подробности см. В статьях моего блога о ковариации и контравариантности. (В понедельник и в четверг на этой неделе появится новая тема по этому вопросу.)

140
ответ дан Eric Lippert 17 August 2018 в 09:16
поделиться
  • 1
    Спасибо помощнику за исправление и предложения, позаботятся в следующий раз. – Asad Butt 30 November 2009 в 02:04
  • 2
    Спасибо за простое и ясное объяснение. – DeeStackOverflow 1 July 2011 в 15:51
  • 3
    Будет ли что-нибудь небезопасное относительно IList & lt; T & gt; или ICollection & lt; T & gt; реализуя неэквивалентный IList, но при внедрении не-generic Ilist / ICollection возвращает True для IsReadOnly и бросает NotSupportedException для любых методов или свойств, которые могли бы его изменить? – supercat 28 July 2011 в 16:40
  • 4
    Хотя этот ответ содержит вполне приемлемые аргументы, он действительно не «истинный». Простой ответ: C # не поддерживает это. Идея иметь список животных, которые содержат жирафов и тигров, совершенно верна. Единственная проблема возникает, когда вы хотите получить доступ к классам более высокого уровня. На самом деле это ничем не отличается от передачи родительского класса в качестве параметра функции, а затем попытки передать его другому классу братьев. Могут быть технические проблемы с реализацией роли, но приведенное выше объяснение не дает никаких оснований, почему это будет плохая идея. – Paul Coldrey 29 January 2016 в 06:55
  • 5
    @jbueno: Прочтите последний абзац моего ответа. Преобразование там известно как безопасное . Зачем? Потому что невозможно превратить последовательность жирафов в последовательность животных, а затем поставить тигра в последовательность животных . IEnumerable<T> и IEnumerator<T> оба помечены как безопасные для ковариации, и компилятор подтвердил это. – Eric Lippert 8 May 2017 в 15:15

Поскольку C # не разрешает этот тип преобразования наследования в данный момент .

0
ответ дан Noon Silk 17 August 2018 в 09:16
поделиться
  • 1
    Во-первых, это вопрос конвертируемости, а не наследования. Во-вторых, ковариация родовых типов не будет работать на типы классов, только на интерфейсах и типах делегатов. – Eric Lippert 30 November 2009 в 01:40
  • 2
    Ну, я с трудом спорю с тобой. – Noon Silk 30 November 2009 в 01:51

Если вы используете IEnumerable вместо этого, он будет работать (по крайней мере, в C # 4.0, я не пробовал предыдущие версии). Это просто бросок, конечно же, он все равно будет списком.

Вместо -

List<A> listOfA = new List<C>(); // compiler Error

В исходном коде вопроса , use -

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)

20
ответ дан PhistucK 17 August 2018 в 09:16
поделиться
  • 1
    Как использовать IEnumerable в этом случае? – Vladius 14 December 2015 в 13:12
  • 2
    Вместо List<A> listOfA = new List<C>(); // compiler Error в исходном коде вопроса введите IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :) – PhistucK 14 December 2015 в 17:39
  • 3
    Способ, принимающий IEnumerable & lt; BaseClass & gt; поскольку параметр позволит унаследованному классу быть переданным в виде списка. Таким образом, очень мало что нужно сделать, но изменить тип параметра. – beauXjames 15 December 2015 в 18:00

Мне лично нравится создавать библиотеки с расширениями для классов

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}
0
ответ дан vikingfabian 17 August 2018 в 09:16
поделиться

Вы можете использовать только списки чтения. Например:

IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works

И вы не можете сделать это для списков, поддерживающих элементы сохранения. Причина в том, что:

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

Что теперь? Помните, что listObject и listString - это тот же самый список, поэтому listString теперь имеет элемент объекта - он не должен быть возможным, и это не так.

9
ответ дан Wojciech Mikołajewicz 17 August 2018 в 09:16
поделиться
  • 1
    Это был самый понятный для меня ответ. Специально потому, что он упоминает, что есть что-то, называемое IReadOnlyList, которое будет работать, потому что оно гарантирует, что больше не будет добавлен элемент. – bendtherules 14 October 2017 в 07:00
  • 2
    Жаль, что вы не можете сделать подобное для IReadOnlyDictionary & lt; TKey, TValue & gt ;. Почему это? – Alastair Maw 30 November 2017 в 18:33
Другие вопросы по тегам:

Похожие вопросы: