Преобразовать список <DerivedClass> в список <BaseClass>
Хотя мы можем наследовать базовый класс/интерфейс, почему мы не можем объявить List<>
используя тот же класс/интерфейс?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
Есть ли способ?
Ответы
Ответ 1
Способ сделать эту работу - перебирать список и лить элементы. Это можно сделать с помощью ConvertAll:
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
Вы также можете использовать Linq:
List<A> listOfA = new List<C>().Cast<A>().ToList();
Ответ 2
Прежде всего, прекратите использовать непонятные для понимания имена классов, такие как A, B, C. Используйте животных, млекопитающих, жирафов или продуктов питания, фруктов, апельсинов или что-то там, где отношения понятны.
Тогда возникает вопрос: "Почему я не могу назначить список жирафов переменной типа списка животных, так как я могу назначить жирафа переменной типа животного?"
Ответ: предположим, вы могли бы. Что тогда может пойти не так?
Ну, вы можете добавить Тигра в список животных. Предположим, мы разрешаем вам помещать список жирафов в переменную, содержащую список животных. Затем вы пытаетесь добавить тигра в этот список. Что происходит? Вы хотите, чтобы список жирафов содержал тигра? Вы хотите сбой? или вы хотите, чтобы компилятор защитил вас от сбоя, сделав назначение незаконным в первую очередь?
Выберем последнее.
Этот вид преобразования называется "ковариантным" преобразованием. В С# 4 мы разрешим вам делать ковариантные преобразования на интерфейсах и делегатах, когда преобразование, как известно, всегда безопасно. Подробности см. В статьях моего блога о ковариации и контравариантности. (В понедельник и в четверг на этой неделе будет новая тема по этой теме.)
Ответ 3
Процитировать великое объяснение Эрика
Что происходит? Вы хотите, чтобы список жирафов содержал тигра? Вы хотите сбой? или вы хотите, чтобы компилятор защитил вас от сбоя, сделав это незаконным в первую очередь? Выберем последнее.
Но что, если вы хотите выбрать для сбоя во время выполнения вместо ошибки компиляции? Обычно вы используете Cast < > или ConvertAll < > , но тогда у вас будет две проблемы: она создаст копию списка. Если вы добавите или удалите что-то в новом списке, это не будет отражено в исходном списке. Во-вторых, существует большая производительность и ограничение памяти, так как он создает новый список с существующими объектами.
У меня была та же проблема, и поэтому я создал класс-оболочку, который может использовать общий список, не создавая совершенно новый список.
В исходном вопросе вы можете использовать:
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);
}
}
Ответ 4
Насколько он не работает, может быть полезно понять ковариацию и контравариантность.
Просто чтобы показать, почему это не должно работать, вот изменение кода, который вы указали:
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>());
}
Ответ 5
Если вы используете IEnumerable
вместо этого, он будет работать (по крайней мере, в С# 4.0, я не пробовал предыдущие версии). Это всего лишь бросок, конечно же, это будет список.
Вместо
List<A> listOfA = new List<C>(); // compiler Error
В исходном коде вопроса используйте -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
Ответ 6
Вы можете использовать только списки для чтения. Например:
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 теперь имеет элемент объекта - он не должен быть возможным, а это не так.
Ответ 7
Мне лично нравится создавать библиотеки с расширениями для классов
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
where TFrom : class
where TTo : class
{
return fromlist.ConvertAll(x => x as TTo);
}
Ответ 8
Потому что С# не разрешает этот тип преобразования inheritance в данный момент.
Ответ 9
Это расширение для 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 code to cast the Dictionary:
return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
}
...
}
Это сработало хорошо. Тем не менее, я в конечном итоге столкнулся с несвязанными ограничениями и в итоге создал абстрактный метод FindChild()
в базовом классе, который вместо этого выполнял поиск. Как оказалось, это устранило потребность в литом словаре в первую очередь. (Я смог заменить его простым IEnumerable
для моих целей.)
Таким образом, вопрос, который вы можете задать (особенно если производительность является проблемой, запрещающей использование .Cast<>
или .ConvertAll<>
), это:
"Мне действительно нужно, чтобы весь коллекцию, или я могу использовать абстрактный метод для хранения специальных знаний, необходимых для выполнения задачи, и тем самым избежать прямого доступа к коллекции?"
Иногда самое простое решение - лучшее.
Ответ 10
Вы также можете использовать пакет NuGet System.Runtime.CompilerServices.Unsafe
для создания ссылки на тот же List
:
using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);
Учитывая приведенный выше пример, вы можете получить доступ к существующим экземплярам Hammer
в списке, используя переменную tools
. Добавление экземпляров Tool
в список вызывает исключение ArrayTypeMismatchException
поскольку tools
ссылаются на ту же переменную, что и hammers
.