it-roy-ru.com

C # Преобразование списка <ObjBase> в список <Obj>

Почему я не могу использовать List<ObjBase> как List<Obj>? Почему не работает следующее:

internal class ObjBase
   {
   }

internal class Obj : ObjBase
   {
   }   

internal class ObjManager
{
    internal List<Obj> returnStuff()
    {
       return getSomeStuff() as List<Obj>;
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }

}

Вместо этого я должен сделать это:

internal class ObjBase
   {
   }

internal class Obj : ObjBase
   {
   }

internal class ObjManager
{
    internal List<Obj> returnStuff()
    {
       List<ObjBase> returnedList = getSomeStuff();
       List<Obj> listToReturn = new List<Obj>(returnedList.Count);
       foreach (ObjBase currentBaseObject in returnedList)
       {
          listToReturn.Add(currentBaseObject as Obj);
       }
       return listToReturn;
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }
}

Я получаю следующую ошибку в Visual Studio 2008 (сокращено для удобства чтения):

Невозможно преобразовать тип «Список» в «Список» посредством преобразования ссылок, преобразования в бокс, преобразования в распаковку, преобразования в оболочку или преобразования нулевого типа

Благодарю.

26
AndrewJacksonZA

Пожалуйста, посмотрите на следующие вопросы:

Общий список .NET Casting

Почему этот общий бросок не удается?

9
gjutras

Вы можете использовать методы расширения Cast и ToList из System.Linq, чтобы поместить это в одну строку.

Вместо 

internal List<Obj> returnStuff()
{
   return getSomeStuff() as List<Obj>;
}

сделай это:

internal List<Obj> returnStuff()
{
   return getSomeStuff().Cast<Obj>().ToList();
}
34
RaYell

Я могу описать «проблему» только из представления Java, но насколько я знаю, этот аспект одинаков как в C #, так и в Java:

List<ObjBase> не является List<Obj>, потому что он может содержать объект ObjBase, который не является объектом Obj.

Другой способ обойти List<Obj> можно не привести к List<ObjBase>, поскольку первый гарантирует принятие вызова Add() с аргументом ObjBase, который последний не примет!

Итак, подведем итог: даже если Obj is-a ObjBase a List<Obj> есть не a List<ObjBase>.

10
Joachim Sauer

Ковариантность, мой друг. Посмотрите на http://blog.t-l-k.com/dot-net/2009/c-sharp-4-covariance-and-contravariance

3
Dewfy

Я думаю, что вы неправильно понимаете актерский состав, который вы пытаетесь сделать. Вы думаете, что вы меняете тип объекта, который хранится в списке, где вы на самом деле пытаетесь изменить тип самого списка. Скорее имеет смысл, что вы не можете изменить сам список, поскольку вы уже заполнили его.

Вы можете рассматривать его как список базового класса, а затем приводить его при обработке элементов списка, это мой подход.

Какова цель этого попытки броска?

1
Lazarus

C # в настоящее время не поддерживает дисперсию для универсальных типов. Из того, что я прочитал, это изменится в 4.0.

Смотрите здесь для получения дополнительной информации о дисперсии в дженериках.

1
Scott Ivey

list.ConvertAll выглядит заманчиво, но имеет один большой недостаток: он создаст целый новый список. Это повлияет на производительность и использование памяти, особенно для больших списков.

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

Использование:

var x = new List<ObjBase>();
var y = x.CastList<ObjBase, Obj>(); // y is now an IList<Obj>

Код для добавления в вашу библиотеку:

public static class Extensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

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; } }
}
1
Bigjim

У Linq есть метод ConvertAll. так что-то вроде 

list.ConvertAll<Obj>(objBase => objbase.ConvertTo(obj));

Я не уверен, что еще предложить. Я предполагаю, что ObjBase является базовым классом, и если все объекты ObjBase являются объектами Obj, я не уверен, зачем вам эти два объекта в первую очередь. Возможно, я не в курсе.

Правка: метод list.Cast будет работать лучше, чем выше, при условии, что они могут быть преобразованы друг к другу. Забыл об этом, пока не прочитал другие ответы.

0
Jimmeh

Лазарь :
Я думал, что компилятор поймет, что мне нужны действия над объектами списка, а не то, что я пытался привести сам список.

Еще немного информации:

public abstract class ObjBase
   {
   }

internal interface IDatabaseObject
   {
   }

public class Obj : ObjBase, IDatabaseObject
   {
   }


internal interface IDatabaseObjectManager
   {
      List<ObjBase> getSomeStuff();
   }

public class ObjManager : IObjManager
{
    public List<Obj> returnStuff()
    {
       return getSomeStuff().Cast <Customer>().ToList<Customer>();
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }
}

Теперь код клиента вне этого DLL может идти: ObjManager objM = new ObjManager (); Список listOB = objM.returnStuff (); Я собираюсь создать несколько типов Obj и ObjManager для этой части (O/RM) приложения.

(У проклятого блока комментариев закончились символы! :-)

0
AndrewJacksonZA

Это основная проблема в C # - так были разработаны дженерики. Список не расширяет Список, это просто совершенно другой тип. Вы не можете ни разыгрывать, ни назначать их друг другу, ваш единственный вариант - скопировать один список в другой.

0
Grzenio

Вот как я исправил конверсию из 

list<SomeOtherObject>

к

object

а затем к

List<object>

https://stackoverflow.com/a/16147909/2307326

0
Andrew Marais