Вопрос

Хотя мы можем наследовать от базового класса / интерфейса, почему мы не можем объявить 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
    }
}

Есть ли какой-нибудь обходной путь?

Это было полезно?

Решение

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

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

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

List<A> listOfA = new List<C>().Cast<A>().ToList();

Другие советы

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

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

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

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

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

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

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

  

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

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

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

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

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);
    }
}

Что касается того, почему это не работает, может быть полезно понять ковариацию и контравариантность .

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

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>());
}

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

Вместо того, чтобы -

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

В исходном коде вопроса используйте -

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

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

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 теперь имеет элемент object - это не должно быть возможно, и это не так.

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

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}

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

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

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

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

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

Вы также можете использовать пакет NuGet System.Runtime.CompilerServices.Unsafe , чтобы создать ссылку на тот же список :

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 .

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top