Lijst converteren<DerivedClass> naar lijst<BaseClass>

Hoewel we kunnen erven van de basisklasse/interface, waarom kunnen we dan geen List<>declareren
dezelfde klasse/interface gebruiken?

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

Is er een omweg?


Antwoord 1, autoriteit 100%

De manier om dit te laten werken, is door de lijst te herhalen en de elementen te casten. Dit kan gedaan worden met ConvertAll:

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

U kunt ook Linq gebruiken:

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

Antwoord 2, autoriteit 67%

Allereerst, stop met het gebruik van onmogelijk te begrijpen klassennamen zoals A, B, C. Gebruik Animal, Mammal, Giraffe of Food, Fruit, Orange of iets anders waarvan de relaties duidelijk zijn.

Uw vraag is dan “waarom kan ik geen lijst van giraffen toewijzen aan een variabele van het type lijst van dieren, aangezien ik een giraffe kan toewijzen aan een variabele van het type dier?”

Het antwoord is: stel dat je dat zou kunnen. Wat kan er dan mis gaan?

Nou, je kunt een tijger toevoegen aan een lijst met dieren. Stel dat we je toestaan ​​een lijst met giraffen in een variabele te plaatsen die een lijst met dieren bevat. Dan probeer je een tijger aan die lijst toe te voegen. Wat gebeurt er? Wil je dat de lijst met giraffen een tijger bevat? Wil je een crash? of wil je dat de compiler je beschermt tegen de crash door de opdracht in de eerste plaats illegaal te maken?

Wij kiezen voor het laatste.

Dit soort conversie wordt een ‘covariante’ conversie genoemd. In C# 4 laten we u covariante conversies maken op interfaces en gedelegeerden wanneer bekend is dat de conversie altijd veilig is. Zie mijn blogartikelen over covariantie en contravariantie voor meer informatie. (Er komt een nieuwe over dit onderwerp op zowel maandag als donderdag van deze week.)


Antwoord 3, autoriteit 24%

Om de geweldige uitleg van Eric te citeren

Wat gebeurt er? Wil je dat de lijst met giraffen een tijger bevat? Wil je een crash? of wil je dat de compiler je beschermt tegen de crash door de opdracht in de eerste plaats illegaal te maken?
Wij kiezen voor het laatste.

Maar wat als u wilt kiezen voor een runtime-crash in plaats van een compileerfout? Normaal gesproken gebruikt u Cast<> of Alles Converteren<> maar dan heb je 2 problemen: Het zal een kopie van de lijst maken. Als u iets toevoegt aan of verwijdert uit de nieuwe lijst, wordt dit niet weergegeven in de oorspronkelijke lijst. En ten tweede is er een grote prestatie en geheugenverlies omdat het een nieuwe lijst met de bestaande objecten creëert.

Ik had hetzelfde probleem en daarom heb ik een wrapper-klasse gemaakt die een generieke lijst kan casten zonder een geheel nieuwe lijst te maken.

In de oorspronkelijke vraag zou je dan kunnen gebruiken:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

en hier de wrapper-klasse (+ een uitbreidingsmethode CastList voor eenvoudig gebruik)

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

Antwoord 4

Persoonlijk maak ik graag bibliotheken met extensies voor de klassen

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

Antwoord 5

Omdat C# dat type overerving-conversie niet toestaat op dit moment.


Antwoord 6

Dit is een uitbreiding op het briljante antwoordvan BigJim.

In mijn geval had ik een NodeBase-klasse met een Children-woordenboek, en ik had een manier nodig om in het algemeen O(1)-zoekopdrachten van de kinderen uit te voeren. Ik probeerde een privé-woordenboekveld terug te geven in de getter van Children, dus ik wilde natuurlijk duur kopiëren/itereren vermijden. Daarom heb ik de code van Bigjim gebruikt om het Dictionary<whatever specific type>te casten naar een generiek 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>();
    }
    ...
}

Dit werkte goed. Uiteindelijk kwam ik echter niet-gerelateerde beperkingen tegen en maakte ik uiteindelijk een abstracte FindChild()-methode in de basisklasse die in plaats daarvan de zoekopdrachten zou uitvoeren. Het bleek dat dit in de eerste plaats de noodzaak van het gegoten woordenboek elimineerde. (Ik kon het vervangen door een eenvoudige IEnumerablevoor mijn doeleinden.)

Dus de vraag die u zou kunnen stellen (vooral als prestaties een probleem zijn dat u verbiedt om .Cast<>of .ConvertAll<>te gebruiken, is:

“Moet ik echt de hele collectie casten, of kan ik een abstracte methode gebruiken om de speciale kennis vast te houden die nodig is om de taak uit te voeren en zo directe toegang tot de collectie te vermijden?”

Soms is de eenvoudigste oplossing de beste.


Antwoord 7

U kunt ook het System.Runtime.CompilerServices.UnsafeNuGet-pakket gebruiken om een ​​verwijzing naar dezelfde Listte maken:

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

Gezien het bovenstaande voorbeeld, hebt u toegang tot de bestaande Hammer-instanties in de lijst met behulp van de variabele tools. Het toevoegen van Tool-instanties aan de lijst genereert een ArrayTypeMismatchException-uitzondering omdat toolsnaar dezelfde variabele verwijst als hammers.


Antwoord 8

Ik heb deze hele thread gelezen, en ik wil je alleen wijzen op wat mij een inconsistentie lijkt.

De compiler voorkomt dat je de opdracht met Lists doet:

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

Maar de compiler is perfect in orde met arrays:

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

Het argument of de opdracht bekend is als veiligvalt hier uiteen. De opdracht die ik deed met de array is niet veilig. Om dat te bewijzen, als ik dat hier opvolg:

myAnimalsArray[1] = new Giraffe();

Ik krijg een runtime-uitzondering“ArrayTypeMismatchException”. Hoe verklaart men dit? Als de compiler echt wil voorkomen dat ik iets stoms doe, had hij me moeten beletten de array-opdracht uit te voeren.

Other episodes