È sbagliato trasmettere un enumeratore di una classe figlio a un enumeratore di una classe genitore?
-
04-07-2019 - |
Domanda
Ho un errore nella mia build che dice:
Errore 12 Impossibile convertire implicitamente genere & 'System.Collections.Generic.IEnumerator lt; & BaseClass gt;' a & 'System.Collections.Generic.IEnumerator lt; & IParentClass gt;'. Esiste una conversione esplicita (sei tu manca un cast?)
È sbagliato semplicemente buttarlo via?
Questo è il mio codice:
public Dictionary<Int32, BaseClass> Map { get; private set; }
public IEnumerator<BaseClass> GetEnumerator()
{
return this.Map.Values.GetEnumerator();
}
public IEnumerator<IParentClass> IEnumerable<IParentClass>.GetEnumerator()
{
return this.GetEnumerator(); // ERROR!
}
La mia domanda è, posso semplicemente cambiare questa riga:
return this.GetEnumerator();
a:
return (IEnumerator<IParentClass>)this.GetEnumerator();
(senza effetti collaterali negativi)?
Risposta accettata:
Ho cambiato la funzione nel modo seguente (dopo aver letto il post di Jon Skeet):
IEnumerator<IParentClass> IEnumerable<IParentClass>.GetEnumerator()
{
return this.Map.Values.Cast<IParentClass>().GetEnumerator();
}
Soluzione
No, non puoi, perché al momento i generici non sono covarianti in C #. .NET stesso ha un po 'di supporto (per delegati e interfacce) ma non è ancora veramente usato.
Se restituissi IEnumerable<BaseClass>
invece di IEnumerator<BaseClass>
(e supponendo .NEt 3.5) potresti usare Enumerable.Cast
- ma al momento dovrai scrivere il tuo metodo di estensione, ad es.
public static IEnumerator<TParent> Upcast<TParent, TChild>
(this IEnumerator<TChild> source)
where TChild : TParent
{
while (source.MoveNext())
{
yield return source.Current;
}
}
In alternativa, nel tuo caso puoi usare Cast in precedenza:
return this.Map.Values.Cast<BaseClass>().GetEnumerator();
Altri suggerimenti
No, non puoi, almeno in C # 3.0 e sotto la varianza dell'interfaccia non è supportata. Guarda l'eccellente serie di Eric Lippert su questo, e in particolare questo .
No, non è sicuro, vedi sotto:
utilizzando System.Collections.Generic; classe Foo {} classe bar: Foo {}
static class Program
{
static IEnumerator<Foo> GetBase() {
yield return new Foo();
yield return new Bar();
}
static IEnumerator<Bar> GetDerived()
{
return (IEnumerator<Bar>)GetBase();
}
static void Main()
{
var obj = GetDerived(); // EXCEPTION
}
}
Tuttavia, dovresti essere in grado di utilizzare un blocco iteratore per eseguire il cast per te?
static IEnumerator<Bar> GetDerived()
{
using (IEnumerator<Foo> e = GetBase())
{
while (e.MoveNext())
{
// or use "as" and only return valid data
yield return (Bar)e.Current;
}
}
}
Per ragionare perché questo non è appropriato, immagine invece di Enumerator
, List
. Entrambi usano generics - il compilatore non gestisce nessuno dei due in modo speciale in relazione agli argomenti generici.
void doStuff() {
List<IParentThing> list = getList();
list.add(new ChildThing2());
}
List<IParentThing> getList() {
return new List<ChildThing1>(); //ERROR!
}
Questo primo metodo va bene: un elenco di IParentThing
dovrebbe essere in grado di ricevere un ChildThing2
. Ma un elenco di ChildThing1
s non è in grado di gestire un List<ChildThing1>
o qualsiasi implementatore di List<IParent>
diverso da <=> - in altre parole, se <=> fosse autorizzato a trasmettere come <=>, devono essere in grado di gestire tutte le sottoclassi di <=>, non solo <=> e <=>.
Nota che i generici Java hanno un modo per dire che " Voglio un elenco di tutto ciò che eredita da questo " oltre a " Voglio un elenco di tutto ciò che questo eredita, " che consente soluzioni più interessanti (e secondo me eleganti) ad alcuni problemi.
IEnumerator<BaseClass>
e IEnumerator<ParentClass>
non sono correlati, anche se i loro parametri generici lo sono. Vorrei invece utilizzare un metodo di estensione LINQ Select
in questo modo:
return this.Select(x => (IParentClass)x).GetEnumerator();
o il Cast
metodo di estensione:
return this.Cast<IParentClass>().GetEnumerator();