944 messaggi dal 11 febbraio 2013
ciao
scusate la domanda ma vorrei capire quando e perchè debbo usare yield return per una enumerazione
piuttosto che implementare IEnumerable

percui avendo la solita classe Persona e una collection class che accoglie l'insieme dei tipi avrei
pressochè due scenari

primo:
class PersonCollection: IEnumerable
    {
        private Person[] _personList;

        public PersonCollection(Person[] personList)
        {
            _personList = new Person[personList.Length];

            for (int i = 0; i < personList.Length ; i++)
            {
                _personList[i] = personList[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }
        public PersonEnumerator GetEnumerator()
        {
            return new PersonEnumerator(_personList);
        }
    }


secondo:
class PersonCollection 
    {
        private Person[] _personList;
        public PersonCollection(Person[] personList)
        {
            _personList = new Person[personList.Length];

            for (int i = 0; i < personList.Length; i++)
            {
                _personList[i] = personList[i];
            }
        }

        public IEnumerable<Person> EnumeratePersons()
        {
            for(int index = 0; index < this._personList.Length; index ++)
            {
                yield return this._personList[index];
            }
        }
    }


cosi con entrambi posso stampare:
 Person[] personArray = new Person[3]
                                                {
                                                    new Person("Hendrik", "Lorentz"),
                                                    new Person("Pieter", "Zeeman"),
                                                    new Person("Marie", "Curie")
                                                };

            PersonCollection personColl = new PersonCollection(personArray);
            foreach(Person p in personColl.EnumeratePersons()) // solo personColl se implemento IEnumerable
            {
                Console.WriteLine(p);
            }


Capisco che con yield return scrive meno e quindi "economicamente" parlando mi conviene
ma in termini di performance ci "guadagno"

perdonate se la domanda non è "intelligente"
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao,
personalmente per una PersonCollection sceglierei la prima soluzione, ovvero implementare IEnumerable. Infatti, da una classe che reca "Collection" nel nome, mi aspetto che sia iterabile con un foreach.

Poi, in altri scenari, puoi sempre avere un metodo che ti restitusce un IEnumerable come il tuo EnumeratePersons.
Prendi per esempio la classe System.IO.Directory che ha una responsabilità più generale rispetto all'agire semplicemente da collezione di file. Infatti al suo interno trovi ben 3 metodi che restituiscono un IEnumerable (EnumerateDirectories per le sottodirectory, EnumerateFiles per i file e EnumerateFileSystemEntries per entrambi).

Sta a te, in base al problema che devi risolvere, decidere se compiere una scelta di design che ti porta ad implementare IEnumerable o a costruire uno o più metodi che restituiscono IEnumerable.



Capisco che con yield return scrive meno e quindi "economicamente" parlando mi conviene

In che senso "scrive meno"?

yield return serve restituire un oggetto nel momento stesso in cui il chiamante lo chiede. Immagina di avere una directory con dentro 1 milione di file: il metodo EnumerateFiles non li va a leggere tutti quanti in memoria, ma avvalendosi di yield return ne legge uno per volta, man mano che il ciclo foreach del chiamante passa all'iterazione successiva. Questo è il modo corretto di leggere grandi insiemi di elementi, perché il chiamante potrebbe anche decidere di interrompere la lettura dopo aver esaminato pochi elementi.

Il metodo GetFiles, invece, va a leggersi tutto il milione di file e te li restituisce in un gigantesco array, causando l'occupazione di un bel quantitativo di memoria. Va usato solo se hai bisogno di fare parecchi accessi randomici.

In ultimo, una considerazione sul tuo esempio:
public IEnumerable<Person> EnumeratePersons()
{
    for(int index = 0; index < this._personList.Length; index ++)
    {
        yield return this._personList[index];
    }
}

Far questo non è molto utile perché _personList già implementa IEnumerable<Person>. Potresti semplicemente fare:
public IEnumerable<Person> EnumeratePersons()
{
    return _personList;
}

In questo caso, restituire IEnumerable<Person> anziché un Person[] se vuoi ridurre la superficie della API che esponi al chiamate. Con il metodo EnumeratePersons() stai dicendo agli utilizzatori della tua classe: "Il mio intendo è farvi leggere l'elenco in maniera forward-only, ma non voglio assolutamente che me lo modifichiate o che lo possiate accedere in maniera randomica, cosa che vi sarebbe consentita se vi esponessi direttamente un array".

ciao,
Moreno

Enjoy learning and just keep making
944 messaggi dal 11 febbraio 2013
volevo dire "scrivo meno codice"

cmq ho capito

illuminante l'esempio con System.IO.Directory

Percui si può dire che un metodo statico con yield return è da preferire nel caso si debba accedere
sequenzialmente un insieme molto grande

grazie tante

ciao

ps:
dalla teoria alla pratica
in un processo di download usavo proprio GetFiles...ora ho sostituito EnumerateFiles cosi
String[] tempFolderItem = Directory.EnumerateFiles(tempfolder, "*.jpg").ToArray();


ed è molto piu veloce
Modificato da jjchuck il 13 dicembre 2017 16.35 -
11.886 messaggi dal 09 febbraio 2002
Contributi

Per cui si può dire che un metodo statico con yield return è da preferire nel caso si debba accedere
sequenzialmente un insieme molto grande

Sì, anche se il metodo non deve essere necessariamente statico. Può anche essere un metodo d'istanza.
Vedi per esempio il metodo EnumerateFiles della classe DirectoryInfo.
https://msdn.microsoft.com/it-it/library/dd383690(v=vs.110).aspx

I benefici, anche se meno evidenti, li ottieni anche con insiemi piccoli.
Prendi l'extension method First() di IEnumerable: sarebbe un peccato se dovesse causare il caricamento in memoria di tutti gli n elementi di una collezione, dato che deve soltanto restituire il primo al chiamante.

in un processo di download usavo proprio GetFiles...ora ho sostituito EnumerateFiles cosi
String[] tempFolderItem = Directory.EnumerateFiles(tempfolder, "*.jpg").ToArray();
ed è molto piu veloce 

Sei sicuro che non sia un'impressione? Qui stai comunque caricando in memoria l'intero insieme di file Jpeg a causa di ToArray(), che non è diverso dall'ottenere l'intero array da GetFiles.
In pratica, il ToArray sta vanificando l'utilità di EnumerateFiles.

L'IEnumerable è come un nastro trasportatore su cui viaggiano degli oggetti. In questo caso gli oggetti sono stringhe ma potrebbero essere bottiglie di birra come in questo caso:
https://youtu.be/RqfLOJGS_7o?t=1m58s
Quando fai un foreach sull'ienumerable, vedi passare gli oggetti uno alla volta allo scopo di fare alcune cose, tipo filtrarli (escludere le birre con il topo dentro), contarli, aggregarli o altro.
Questo è abbastanza efficiente, perché gli oggetti vengono snocciolati uno alla volta, man mano che il foreach avanza.

Quindi la mia domanda è: cosa devi fare con l'array tempFolderItem e perché deve essere un array anziché un IEnumerable<string>? Devi accedere in maniera randomica ai file? Devi passarla a qualche metodo che richiede un array di stringhe anziché un ienumerable?

ciao,
Moreno

Enjoy learning and just keep making
944 messaggi dal 11 febbraio 2013
Non è un'impressione perchè ho contato i secondi

ho bisogno di filtrare le immagini in cartella in base al fatto che l'utente tratti o meno certe linee di prodotto
 string[] listaFoto = Directory.EnumerateFiles(fotoPath, "*.jpg").ToArray();

            foreach (var f in fotoUtente)
            {
                foreach (var i in listaFoto) // listaFoto è una lista di articoli trattati dall'utente
                {
                    string nomeFoto = i.Substring(fotoPath.Length);
                    if (nomeFoto == f)
                    {   //COPIO IMMAGINI
                        File.Copy(Path.Combine(fotoPath, nomeFoto), Path.Combine(tempfolder, nomeFoto.Replace("1Stampa", "")), true);
                       
                    }
                }
            }




ovviamente ho apprezzato molto il video...sono cresciuto con loro

tuttavia non appena mi hai risposto ho pensato
https://youtu.be/mn60YWO218k?t=2m


cmq ho sostituito questo all'array...
IEnumerable<string> listaFoto = Directory.EnumerateFiles(fotoPath, "*.jpg");

e confermo che è il doppio piu lento

ciao e grazie 1000
Modificato da jjchuck il 13 dicembre 2017 21.38 -
Modificato da jjchuck il 13 dicembre 2017 21.42 -
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao,


cmq ho sostituito questo all'array...
IEnumerable<string> listaFoto = Directory.EnumerateFiles(fotoPath, "*.jpg");
e confermo che è il doppio piu lento

Sì, ma c'è un motivo. Guarda i foreach:
foreach (var f in fotoUtente) {
  foreach (var i in listaFoto) {

Dato che il foreach su listaFoto è quello più interno, stai di fatto estraendo le jpeg dal disco molte volte, una per ogni elemento trovato in fotoUtente.
Infatti, ogni volta che fai un foreach su un IEnumerable, ricominci l'iterazione da capo, scatenando di nuovo l'esecuzione del codice che ti fornisce gli elementi. Dato che in questo caso è coinvolta una periferica di I/O (il disco), è normale che si verifichi questa lentezza.

Grazie al ToArray() hai di fatto creato una sorta di "cache in memoria", per cui ogni successivo foreach verrà effettuato nei confronti di una lista di oggetti residente in RAM, che è molto più veloce da accedere rispetto al disco. Il prezzo che hai pagato, però, è quello di un maggiore occupazione di memoria. Al giorno d'oggi consumare qualche MB di memoria in più non è un problema ma questo non cambia il fatto che l'applicazione stia sprecando risorse inutilmente.

La soluzione ideale sarebbe quella di portare all'esterno i ciclo sulle immagini, che è quello più dispendioso da ottenere.
//Questa prima istruzione è molto, molto rapida. Nessuna immagine è stata ancora letta
IEnumerable<string> listaFoto = Directory.EnumerateFiles(fotoPath, "*.jpg");

//La lettura inizia qui. Ad ogni iterazione del foreach viene letto 1 elemento
foreach (var i in listaFoto)
{
  foreach (var f in fotoUtente)
  {
    string nomeFoto = i.Substring(fotoPath.Length);
    if (nomeFoto == f)
    {
      File.Copy(Path.Combine(fotoPath, nomeFoto), Path.Combine(tempfolder, nomeFoto.Replace("1Stampa", "")), true);
    }
  }
}

Prova a misurare le prestazioni di questo algoritmo. Dovrebbe essere giusto un pelo più veloce a quello basato sull'array ma impercettibilmente. Dovrai misurare con lo Stopwatch.
Il vero vantaggio è l'aver evitato il caricamento in memoria della lista di immagini.

Nota: sto facendo questi ragionamenti senza sapere cosa sia la collezione fotoUtente e supponendo che sia un array.

ciao,
Moreno
Modificato da BrightSoul il 14 dicembre 2017 21.27 -

Enjoy learning and just keep making
944 messaggi dal 11 febbraio 2013
Ho notato infatti che in debug usando EnumerateFiles non vedo gli elementi nel ciclo
(forse da qui dovevo già capire)

cmq ho provato ed è piu lento (inoltre si corrompe lo zip)


string[] articoliUtente = {"codiceInesistente"};
            using (MyDataBaseEntities db = new MyDataBaseEntities())
            {
               
                var divisioniUtente = (from d in db.Linee
                                       where d.Codcli == utente
                                       select d.Linea).Distinct();
                articoliUtente = db.TABELLAprodotti.Where(item => divisioniUtente.Contains(item.divisione)).
                                                  Select(i => i.codice).ToArray();

            }

            for (int i = 0; i < articoliUtente.Length; i++)
            {
                articoliUtente[i] = articoliUtente[i] + "1Stampa.jpg";
            }

           ...
            IEnumerable<string> listaFoto = Directory.EnumerateFiles(fotoPath, "*.jpg");
           ...
            var watch = System.Diagnostics.Stopwatch.StartNew();
            
            foreach (var i in listaFoto)
            {             
            foreach (var f in articoliUtente)
            {
                string nomeFoto = i.Substring(fotoPath.Length);
                if (nomeFoto == f) //COPIO IMMAGINI
                    {   
                        File.Copy(Path.Combine(fotoPath, nomeFoto), Path.Combine(tempfolder, nomeFoto.Replace("1Stampa", "")), true);
                    }
                }
            }
            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            Session["t"] = elapsedMs;
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao,


cmq ho provato ed è piu lento (inoltre si corrompe lo zip)

La causa dev'essere in qualche altro punto perché all'interno dei foreach vedo solo un File.Copy. Nel codice che hai postato non c'è nulla che potrebbe causare eccessiva lentezza o corruzione di un file zip. A proposito: dov'è che viene creato il file zip?



for (int i = 0; i < articoliUtente.Length; i++)
{
articoliUtente[i] = articoliUtente[i] + "1Stampa.jpg";
}

Questo ciclo for puoi eliminarlo se nella query LINQ in alto fai così:

articoliUtente = db.TABELLAprodotti.Where(item => divisioniUtente.Contains(item.divisione)).
Select(i => i.codice + "1Stampa.jpg").ToArray();

Questa soluzione serve solo a ridurre righe di codice. A meno che gli articoliUtente non siano milioni, non avrà un impatto prestazionale apprezzabile.

ciao,
Moreno

Enjoy learning and just keep making

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.