[C#] List <DerivedClass>를 List <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
    }
}

주위에 방법이 있습니까?



답변

이 작업을 수행하는 방법은 목록을 반복하고 요소를 캐스트하는 것입니다. 이 작업은 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 <>을 사용하지만 두 가지 문제가 있습니다. 목록의 사본이 작성됩니다. 새 목록에서 무언가를 추가하거나 제거하면 원래 목록에 반영되지 않습니다. 둘째, 기존 객체로 새 목록을 작성하므로 성능과 메모리가 크게 저하됩니다.

나는 같은 문제가 있었으므로 완전히 새로운 목록을 만들지 않고 일반 목록을 캐스팅 할 수있는 래퍼 클래스를 만들었습니다.

원래 질문에서 다음을 사용할 수 있습니다.

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


답변

IEnumerable대신 사용 하면 작동합니다 (적어도 C # 4.0에서는 이전 버전을 시도하지 않았습니다). 물론 이것은 캐스트 일 뿐이며 여전히 목록이 될 것입니다.

대신에 –

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

질문의 원래 코드에서-

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


답변

왜 작동하지 않는 한 공분산과 반공산 을 이해하는 것이 도움이 될 수 있습니다 .

이것이 작동 하지 않는 이유를 보여주기 위해 제공 한 코드가 다음과 같이 변경되었습니다.

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<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에는 이제 객체 요소가 있습니다. 가능하지 않아야합니다.


답변

나는 개인적으로 클래스 확장을 가진 라이브러리를 만들고 싶다

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