List <DerivedClass>를 List <BaseClass>로 변환


179

기본 클래스 / 인터페이스에서 상속 할 수 있지만 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
    }
}

주위에 방법이 있습니까?


답변:


230

이 작업을 수행하는 방법은 목록을 반복하고 요소를 캐스트하는 것입니다. 이 작업은 ConvertAll을 사용하여 수행 할 수 있습니다.

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

Linq를 사용할 수도 있습니다.

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

2
다른 옵션 : List <A> listOfA = listOfC.ConvertAll (x => (A) x);
ahaliav fox

6
어느 것이 더 빠릅니까? ConvertAll또는 Cast?
Martin Braun

24
목록 사본이 생성됩니다. 새 목록에서 무언가를 추가하거나 제거하면 원래 목록에 반영되지 않습니다. 둘째, 기존 객체로 새 목록을 작성하므로 성능과 메모리가 크게 저하됩니다. 이러한 문제가없는 솔루션에 대해서는 아래 답변을 참조하십시오.
Bigjim

modiX에 대한 답변 : ConvertAll은 List <T>의 메소드이므로 일반 목록에서만 작동합니다. IEnumerable 또는 IEnumerable <T>에서는 작동하지 않습니다. ConvertAll은 캐스팅뿐만 아니라 사용자 정의 변환도 수행 할 수 있습니다 (예 : ConvertAll (inches => inches * 25.4)). Cast <A>는 LINQ 확장 방법이므로 모든 IEnumerable <T>에서 작동하며 (일반이 아닌 IEnumerable에서도 작동) 대부분의 LINQ와 마찬가지로 지연된 실행을 사용합니다. 즉, 많은 항목 만 변환합니다. 검색했습니다. 그것에 대해 자세히 알아보십시오 : codeblog.jonskeet.uk/2011/01/13/…
Edward

172

우선, A, B, C와 같이 이해하기 어려운 클래스 이름은 사용하지 마십시오. 동물, 포유류, 기린, 음식, 과일, 오렌지 또는 관계가 분명한 것을 사용하십시오.

당신의 질문은 "왜 기린 목록을 동물 유형의 변수에 할당 할 수 없습니까? 기린을 동물 유형의 변수에 할당 할 수 있기 때문입니다."

대답은 : 당신이 할 수 있다고 가정합니다. 그러면 무엇이 잘못 될 수 있습니까?

글쎄, 호랑이를 동물 목록에 추가 할 수 있습니다. 동물 목록을 보유한 변수에 기린 목록을 넣을 수 있다고 가정합니다. 그런 다음 해당 목록에 호랑이를 추가하려고합니다. 무슨 일이야? 기린 목록에 호랑이가 포함되도록 하시겠습니까? 충돌을 원하십니까? 아니면 처음부터 할당을 불법으로 만들어 컴파일러가 충돌로부터 보호하기를 원하십니까?

우리는 후자를 선택합니다.

이러한 종류의 변환을 "공변량"변환이라고합니다. C # 4 에서는 변환이 항상 안전한 것으로 알려진 경우 인터페이스 및 델리게이트에서 공변량 변환을 수행 할 수 있습니다 . 자세한 내용은 공분산 및 공분산에 대한 내 블로그 기사를 참조하십시오. 이번 주 월요일과 목요일에이 주제에 대한 새로운 내용이 있습니다.


3
제네릭이 아닌 IList를 구현하는 IList <T> 또는 ICollection <T>에 대해 안전하지 않은 것이 있습니까? 그러나 제네릭이 아닌 Ilist / ICollection이 IsReadOnly에 대해 True를 반환하고이를 수정하는 모든 메서드 나 속성에 대해 NotSupportedException을 throw하도록 구현하는 것이 있습니까?
supercat

33
이 답변에는 상당히 수용 가능한 추론이 포함되어 있지만 실제로는 "참"이 아닙니다. 간단한 대답은 C #이 이것을 지원하지 않는다는 것입니다. 기린과 호랑이를 포함하는 동물 목록을 가지고 있다는 아이디어는 완벽하게 유효합니다. 더 높은 수준의 클래스에 액세스하려는 경우에만 문제가 발생합니다. 실제로 이것은 부모 클래스를 매개 변수로 함수에 전달한 다음 다른 형제 클래스에 캐스트하는 것과 다르지 않습니다. 캐스트를 구현하는 데 기술적 인 문제가있을 수 있지만 위의 설명이 나쁜 생각이 드는 이유를 제공하지는 않습니다.
Paul Coldrey

2
@EricLippert 왜 우리 IEnumerable대신 에이 변환을 사용할 수 List있습니까? 즉 : List<Animal> listAnimals = listGiraffes as List<Animal>;불가능하지만 IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>작동합니다.
LINQ

3
@ jbueno : 내 대답의 마지막 단락을 읽으십시오. 이 변환은 안전한 것으로 알려져 있습니다 . 왜? 일련의 기린을 일련의 동물 로 바꾸고 호랑이를 일련의 동물로 만드는 것은 불가능하기 때문입니다 . IEnumerable<T>그리고 IEnumerator<T>모두 공분산 안전으로 표시되며, 컴파일러는 것을 확인했다.
Eric Lippert

60

에릭의 위대한 설명을 인용하자면

무슨 일이야? 기린 목록에 호랑이가 포함되도록 하시겠습니까? 충돌을 원하십니까? 아니면 처음부터 할당을 불법으로 만들어 컴파일러가 충돌로부터 보호하기를 원하십니까? 우리는 후자를 선택합니다.

그러나 컴파일 오류 대신 런타임 충돌을 선택하려면 어떻게해야합니까? 일반적으로 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);
    }
}

1
방금 MVC 뷰 모델로 사용했으며 보편적 인 면도기 부분 뷰가 있습니다. 놀라운 생각입니다! 나는 이것을 읽어서 너무 기쁘다.
Stefan Cebulak

5
클래스 선언에 "where TTo : TFrom"만 추가하여 컴파일러가 잘못된 사용법에 대해 경고 할 수 있습니다. 관련이없는 유형의 CastedList를 만드는 것은 어쨌든 의미가 없으며 "CastedList <TBase, TDerived>"를 만드는 것은 쓸모가 없습니다. 일반 TBase 객체를 추가 할 수 없으며 원래 List에서 가져온 TDerived는 이미 다음과 같이 사용할 수 있습니다. TBase.
Wolfzoon

2
@PaulColdrey Alas, 6 년의 헤드 스타트는 비난의 대상입니다.
Wolfzoon

@ 울프 준 : 표준 .Cast <T> ()도이 제한이 없습니다. .Cast와 같은 동작을 만들고 싶기 때문에 Animals 목록을 Tigers 목록으로 캐스팅하고 기린을 포함하는 경우 예외가 있습니다. 캐스트와 마찬가지로 ...
Bigjim

안녕 @Bigjim, 래퍼 클래스를 사용하여 기존 기린 배열을 동물 배열 (기본 클래스)로 캐스팅하는 방법을 이해하는 데 문제가 있습니다. 모든 팁 : appriciated됩니다
데니스 Vitez

28

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

대신에 -

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

질문의 원래 코드에서-

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


이 경우 IEnumerable을 정확하게 사용하는 방법은 무엇입니까?
Vladius

1
List<A> listOfA = new List<C>(); // compiler Error질문의 원래 코드 대신IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
PhistucK를

IEnumerable <BaseClass>를 매개 변수로 사용하는 메서드는 상속 된 클래스를 List로 전달할 수 있습니다. 따라서 할 일이 거의 없지만 매개 변수 유형을 변경하십시오.
beauXjames

27

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

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

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

"이것이 효과가 있을까?" -예, 아니요 코드를 작성할 수 없어서 컴파일 타임에 실패 할 이유가 전혀 없습니다. 왜냐하면 FirstItem에 'C'유형으로 액세스 할 때 실제로 유효하지 않은 변환을 수행하기 때문입니다. 지원되는 C #을 폭파하는 많은 유사한 방법이 있습니다. 경우 다음 bigjim의 아래에 대답 당신이 실제로 좋은 이유에 대해이 기능을 달성하고자하는 (다수가) 굉장합니다.
Paul Coldrey

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


1
이것은 나에게 가장 이해하기 쉬운 대답이었습니다. 특히 IReadOnlyList라는 것이 언급되어 있기 때문에 더 이상 요소가 추가되지 않기 때문에 작동합니다.
bendtherules

IReadOnlyDictionary <TKey, TValue>에 대해 비슷한 작업을 수행 할 수 없다는 것은 부끄러운 일입니다. 왜 그런 겁니까?
Alastair Maw

이것은 좋은 제안입니다. 편의상 List <>를 어디에나 사용하지만 대부분 읽기 전용 이길 원합니다. 이 캐스트를 허용하고 읽기 전용을 지정하는 위치를 개선하여 두 가지 방법으로 코드를 향상시킬 수 있으므로 좋은 제안입니다.
Rick Love

1

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

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

0

C #에서는 해당 유형을 허용하지 않기 때문에 계승변환 순간 .


6
첫째, 이것은 상속이 아니라 전환 가능성의 문제입니다. 둘째, 제네릭 형식의 공분산은 클래스 형식에서는 작동하지 않으며 인터페이스 및 대리자 형식에서만 작동합니다.
Eric Lippert

5
글쎄, 난 당신과 거의 논쟁 할 수 없습니다.
Noon Silk

0

이것은 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<>)입니다 :

"실제로 전체 컬렉션을 캐스팅해야합니까, 아니면 추상적 인 방법을 사용하여 작업을 수행하는 데 필요한 특수 지식을 보유하여 컬렉션에 직접 액세스하지 않아도됩니까?"

때로는 가장 간단한 해결책이 가장 좋습니다.


0

System.Runtime.CompilerServices.UnsafeNuGet 패키지를 사용하여 동일한 참조를 만들 수도 있습니다 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예외 가 발생 합니다.toolshammers


0

이 전체 스레드를 읽었으며 불일치하는 것이 무엇인지 지적하고 싶습니다.

컴파일러는 Lists를 사용하여 할당을 수행하지 못하게합니다.

List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList;    // Compiler error

그러나 컴파일러는 배열을 완벽하게 사용합니다.

Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray;    // No problem

과제가 안전한 것으로 알려져 있는지에 대한 논쟁은 여기서 분리됩니다. 내가 배열로 한 임무 는 안전하지 않다 . 이를 증명하기 위해 다음과 같이하면 :

myAnimalsArray[1] = new Giraffe();

내가 얻을 런타임 예외 "ArrayTypeMismatchException을". 이것을 어떻게 설명합니까? 컴파일러가 실제로 바보 같은 일을하지 못하게하려면 배열 할당을 수행하지 못하게해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.