Domanda

Sebbene possiamo ereditare dalla classe / interfaccia di base, perché non possiamo dichiarare un Elenco < > usando la stessa classe / interfaccia?

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

C'è un modo per aggirare?

È stato utile?

Soluzione

Il modo per farlo funzionare è iterare l'elenco e lanciare gli elementi. Questo può essere fatto usando ConvertAll:

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

Puoi anche usare Linq:

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

Altri suggerimenti

Prima di tutto, smetti di usare nomi di classe impossibili da capire come A, B, C. Usa Animal, Mammal, Giraffe o Food, Fruit, Orange o qualcosa in cui le relazioni sono chiare.

La tua domanda è quindi "perché non posso assegnare un elenco di giraffe a una variabile di tipo elenco di animali, dato che posso assegnare una giraffa a una variabile di tipo animale? "

La risposta è: supponi di poterlo fare. Cosa potrebbe andare storto?

Bene, puoi aggiungere una tigre a un elenco di animali. Supponiamo che ti consentiamo di mettere un elenco di giraffe in una variabile che contiene un elenco di animali. Quindi si tenta di aggiungere una tigre a tale elenco. Che succede? Vuoi che l'elenco delle giraffe contenga una tigre? Vuoi un incidente? o vuoi che il compilatore ti protegga dall'incidente rendendo in primo luogo il compito illegale?

Scegliamo quest'ultimo.

Questo tipo di conversione è chiamata "covariante" conversione. In C # 4 ti consentiremo di effettuare conversioni covarianti su interfacce e delegati quando la conversione è sempre sicura . Vedi i miei articoli sul blog sulla covarianza e la contraddizione per i dettagli. (Ce ne sarà uno nuovo su questo argomento sia il lunedì che il giovedì di questa settimana.)

Per citare la grande spiegazione di Eric

  

Cosa succede? Vuoi che l'elenco delle giraffe contenga una tigre? Vuoi un incidente? o vuoi che il compilatore ti protegga dall'incidente rendendo in primo luogo il compito illegale?   Scegliamo quest'ultimo.

Ma cosa succede se si desidera scegliere un arresto anomalo del runtime anziché un errore di compilazione? Normalmente useresti Cast < > o Converti Tutti < > ma poi avrai 2 problemi: creerà una copia dell'elenco. Se aggiungi o rimuovi qualcosa nel nuovo elenco, ciò non si rifletterà nell'elenco originale. E in secondo luogo, c'è una grande penalità in termini di prestazioni e memoria poiché crea un nuovo elenco con gli oggetti esistenti

Ho avuto lo stesso problema e quindi ho creato una classe wrapper in grado di trasmettere un elenco generico senza creare un elenco completamente nuovo.

Nella domanda originale è quindi possibile utilizzare:

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

e qui la classe wrapper (+ un metodo di estensione CastList per un facile utilizzo)

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

Per quanto riguarda il motivo per cui non funziona, potrebbe essere utile comprendere covarianza e contravarianza .

Solo per mostrare perché questo non dovrebbe funzionare, ecco una modifica al codice che hai fornito:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

Dovrebbe funzionare? Il primo elemento dell'elenco è di tipo "B", ma il tipo di elemento DerivedList è C.

Ora, supponiamo che vogliamo davvero fare una funzione generica che funzioni su un elenco di qualche tipo che implementa A, ma non ci interessa che tipo sia:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}

Se invece usi IEnumerable , funzionerà (almeno in C # 4.0, non ho provato le versioni precedenti). Questo è solo un cast, ovviamente, rimarrà comunque un elenco.

Invece di -

Elenco < A > listOfA = new List < C > (); // Errore del compilatore

Nel codice originale della domanda, utilizzare -

IEnumerable < A > listOfA = new List < C > (); // errore del compilatore - non di più! :)

Puoi trasmettere solo elenchi di sola lettura. Ad esempio:

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

E non puoi farlo per gli elenchi che supportano il salvataggio di elementi. Il motivo è:

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

E adesso? Ricorda che listObject e listString sono in realtà lo stesso elenco, quindi listString ora ha un elemento object: non dovrebbe essere possibile e non lo è.

Personalmente mi piace creare librerie con estensioni alle classi

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

Perché C # non consente quel tipo di ereditarietà conversione al momento .

Questa è un'estensione della brillante risposta di BigJim .

Nel mio caso avevo una classe NodeBase con un dizionario Children e avevo bisogno di un modo per fare genericamente ricerche O (1) dai bambini. Stavo tentando di restituire un campo di dizionario privato nel getter di Children , quindi ovviamente volevo evitare costose copie / iterazioni. Pertanto ho usato il codice di Bigjim per lanciare il Dizionario < qualunque sia il tipo specifico > in un Dizionario < NodeBase > generico

:

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

Questo ha funzionato bene. Tuttavia, alla fine mi sono imbattuto in limitazioni non correlate e ho finito per creare un metodo astratto FindChild () nella classe base che avrebbe invece effettuato le ricerche. Come si è scoperto, ciò ha eliminato in primo luogo la necessità del dizionario cast. (Sono stato in grado di sostituirlo con un semplice IEnumerable per i miei scopi.)

Quindi la domanda che potresti porre (specialmente se le prestazioni sono un problema che ti proibisce di usare .Cast < > o .ConvertAll < > ) è:

" Devo davvero lanciare l'intera raccolta o posso usare un metodo astratto per conservare le conoscenze speciali necessarie per eseguire l'attività ed evitare quindi di accedere direttamente alla raccolta? "

A volte la soluzione più semplice è la migliore.

È inoltre possibile utilizzare il pacchetto NuGet System.Runtime.CompilerServices.Unsafe per creare un riferimento allo stesso Elenco :

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

Dato l'esempio sopra, puoi accedere alle istanze Hammer esistenti nell'elenco usando la variabile tools . L'aggiunta di istanze Tool all'elenco genera un'eccezione ArrayTypeMismatchException perché tools fa riferimento alla stessa variabile di hammers .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top