È sbagliato trasmettere un enumeratore di una classe figlio a un enumeratore di una classe genitore?

StackOverflow https://stackoverflow.com/questions/229656

  •  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();
        }
È stato utile?

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&lt;ChildThing1> o qualsiasi implementatore di List&lt;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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top