리스트 변환 목록으로
기본 클래스 / 인터페이스에서 상속 할 수 있지만 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);
}
}
왜 작동하지 않는 한 공분산과 공분산 을 이해하는 것이 도움이 될 수 있습니다 .
이것이 작동 하지 않는 이유를 보여주기 위해 제공 한 코드가 다음과 같이 변경되었습니다.
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! :)
You can only cast to readonly lists. For example:
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
And you cannot do it for lists that support saving elements. The reason why is:
List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());
What now? Remember that listObject and listString are the same list actually, so listString now have object element - it shouldn't be possible and it's not.
I personally like to create libs with extensions to the classes
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
where TFrom : class
where TTo : class
{
return fromlist.ConvertAll(x => x as TTo);
}
Because C# doesn't allow that type of inheritance conversion at the moment.
This is an extension to BigJim's brilliant answer.
In my case I had a NodeBase
class with a Children
dictionary, and I needed a way to generically do O(1) lookups from the children. I was attempting to return a private dictionary field in the getter of Children
, so obviously I wanted to avoid expensive copying/iterating. Therefore I used Bigjim's code to cast the Dictionary<whatever specific type>
to a generic 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>();
}
...
}
This worked well. However, I eventually ran into unrelated limitations and ended up creating an abstract FindChild()
method in the base class that would do the lookups instead. As it turned out this eliminated the need for the casted dictionary in the first place. (I was able to replace it with a simple IEnumerable
for my purposes.)
So the question you might ask (especially if performance is an issue prohibiting you from using .Cast<>
or .ConvertAll<>
) is:
"Do I really need to cast the entire collection, or can I use an abstract method to hold the special knowledge needed to perform the task and thereby avoid directly accessing the collection?"
Sometimes the simplest solution is the best.
You can also use the System.Runtime.CompilerServices.Unsafe
NuGet package to create a reference to the same List
:
using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);
Given the sample above, you can access the existing Hammer
instances in the list using the tools
variable. Adding Tool
instances to the list throws an ArrayTypeMismatchException
exception because tools
references the same variable as hammers
.
참고URL : https://stackoverflow.com/questions/1817300/convert-listderivedclass-to-listbaseclass
'Programming' 카테고리의 다른 글
프래그먼트 내의 onCreateOptionsMenu (0) | 2020.06.02 |
---|---|
SQL Server 2008에서 복합 기본 키를 만드는 방법 (0) | 2020.06.02 |
PHP-파일을 서버의 다른 폴더로 이동 (0) | 2020.06.02 |
iPhone에서 최초 앱 실행을 감지하는 방법 (0) | 2020.06.02 |
트위터 부트 스트랩 모달 : 슬라이드 다운 효과를 제거하는 방법 (0) | 2020.06.02 |