IEnumerable risultato

di il
14 risposte

IEnumerable risultato

Ciao a tutti,
ho riscontrato una stranezza ma che probabilmente dipende dal mio codice visual basic express:
Dim PanelCollection = From Control In GroupBox6.Controls Where TypeOf Control Is Panel Select Control
Dim RadioCollection As System.Collections.Generic.IEnumerable(Of Object) = Nothing
Dim EnabledButton As System.Collections.Generic.IEnumerable(Of Object) = Nothing
        
For Each Panel As Control In PanelCollection 
            RadioCollection = From Control In Panel.Controls Where TypeOf Control Is RadioButton Select Control
          
            'If RadioCollection.Count = 0 Then
            'RadioCollection = Nothing
            'End If

            If RadioCollection.Count <> 0 Then                              'Is Not RadioCollection Is Nothing Then
                EnabledButton = From Item In RadioCollection.Cast(Of RadioButton)() Where Item.Enabled = True AndAlso Item.Text = "FAIL" AndAlso Item.Checked = True
                If EnabledButton.Count = 0 Then 'than ' Is Nothing Then
                    MsgBox("collezione di radiobutton nulla. ")
                Else
                    MsgBox("almeno uno dei radiobutton è FAIL è Enabled e Checked. " & EnabledButton.Count)
                End If
            End If
        Next
Si tratta di un gropbox con all'interno dei panel con 2 radiobutton ognuno. La particolarità è che se il risultato della ricerca From Control In Panel.Controls Where TypeOf Control Is RadioButton Select Control non fornisce nessun elemento (Panel vuoto), l'insieme RadioCollection non è uguale a Nothing (parte commentata).
Volevo capire il perché in quanto lo stesso codice lo vorrei utilizzare in un altro programma.

Grazie.

14 Risposte

  • Re: IEnumerable risultato

    KronS ha scritto:


    La particolarità è che se il risultato della ricerca From Control In Panel.Controls Where TypeOf Control Is RadioButton Select Control non fornisce nessun elemento (Panel vuoto), l'insieme RadioCollection non è uguale a Nothing (parte commentata).
    Esaminiamo nel dettaglio questa parte:
    RadioCollection = From Control In Panel.Controls Where TypeOf Control Is RadioButton Select Control
    Quella che tu hai scritto è una espressione LINQ: essa esprime appunto una interrogazione sulla collezione dei Controls del pannello filtrando quelli che sono di tipo RadioButton e prendendo alla fine il riferimento al controllo stesso come risultato.

    La variabile che tu chiami RadioCollection ha un nome fuorviante: in quella variabile non vengono messi i risultati dell'espressione che hai scritto, ma un oggetto che rappresenta appunto la "query" definita tramite l'espressione LINQ, e questa ovviamente è sempre diversa da Nothing.

    Va inoltre detto che, definendo l'espressione LINQ e assegnandola a una variabile, essa non è stata ancora eseguita: le espressioni LINQ sono sottoposte alla cosiddetta "deferred execution" (ho scritto un articolo a tal proposito pubblicato qui).

    Detto questo, come si ottengono quindi i risultati? Nel momento in cui tu esegui un metodo LINQ come First(), Last(), ToList(), ToArray(), ToDictionary(), Count() ecc., oppure nel momento in cui esegui un ciclo foreach sull'espressione (che espone un Enumerable del tipo degli oggetti corrispondenti a quanto proiettato tramite Select), ecco che l'espressione viene interpretata dal "motore" di riferimento ("LINQ To Objects" se si tratta di oggetti, come i controlli, ma c'è anche "LINQ To SQL", "LINQ To XML", ecc.) e ti vengono forniti i risultati richiesti.

    Riassumendo sinteticamente, non ha senso verificare che l'espressione sopra sia diversa da Nothing: quella sintassi è una scorciatoia per inizializzare un oggetto che rappresenta l'operazione da eseguire (tecnicamente è un "Expression Tree"), e pertanto è sempre diversa da Nothing, poiché non rappresenta i suoi risultati come si tende a credere.

    Per ottenerli occorre chiamare uno dei metodi che estrae le informazioni rappresentate dall'espressione, o inserirla in un ciclo (tu ad esempio hai invocato Count()): solo in quel momento essa viene eseguita dal motore LINQ.

    Ciao!
  • Re: IEnumerable risultato

    Devo dire che sono caduto nella "trappola". L'unica mia scusa è che non conoscevo le espressioni LINQ, scusa che non userò più dopo la tua esaudiente spiegazione.
    Se posso vorrei approfittare chiedendoti come potrei modificare il codice se volessi raccogliere tutti i radiobutton (anche se in panel diversi) in una collezione (idea iniziale) come probabilmente hai intuito.

    Ti ringrazio, e complimenti per l'articolo.
  • Re: IEnumerable risultato

    KronS ha scritto:


    Se posso vorrei approfittare chiedendoti come potrei modificare il codice se volessi raccogliere tutti i radiobutton (anche se in panel diversi) in una collezione (idea iniziale) come probabilmente hai intuito.
    Direi che potresti predisporre una query di questo tipo:
    
    Dim Query =
    	From Control In MyForm.Controls Where Typeof Control Is Panel
    	Let Panel = DirectCast(Control, Panel)
    	From Child In Panel.Controls Where Typeof Child Is RadioButton
    	Select DirectCast(Child, RadioButton)
    
    In pratica, enumeri i controlli del Form selezionando solo quelli che sono un Panel; eseguendo un "direct cast", memorizzi nella variabile di range Panel il riferimento al pannello corrente preso in esame; successivamente, si enumerano tutti i controlli del Panel di tipo RadioButton eseguendo anche per questi un "direct cast", in modo che la query finale sia tipizzata, ovvero restituisca un insieme di RadioButton piuttosto che generici Control o Object.

    Così come è composta, questa query ti restituisce un insieme di RadioButton trovati per ogni Panel: siccome entrambi sono presenti in quantità multipla, quello che ottieni è quindi una "enumerazione di enumerazioni", precisamente Enumerable(Of Enumerable(Of RadioButton)).

    Per scandire i risultati, quindi, dovresti fare due cicli foreach() innestati.

    Tuttavia, LINQ prevede anche un metodo che risolve esattamente questi problemi, "appiattendo" le enumerazioni multiple di elementi dello stesso tipo e combinandole all'interno di una sola enumerazione.

    Scrivendo quindi
    
    Query.SelectMany(Function (r) {r})
    
    ottieni una espressione che combina le clausole riportate sopra appiattendo la lista, che diventa quindi una Enumerable(Of RadioButton).

    Ciò che è stato fatto fino a questo momento non è altro che la creazione dell'espressione e il suo arricchimento aggiungendo delle clausole, ma ancora non è stata eseguita per ottenere i risultati.

    Quando invocherai uno dei metodi indicati nella mia risposta precedente, il motore LINQ lavorerà sugli oggetti in questione (i controlli del Form) e tirerà fuori i risultati che ti servono.

    In alternativa, potresti usare la forma con metodi e funzioni lambda (la sintassi LINQ viene trasformata alla fine in questa forma automaticamente dal compilatore) modificando un pochino i filtri e l'ordine delle operazioni; ad esempio
    
    Dim RadioQuery = MyForm.Controls.OfType(Of Control)() _
    	.Where(Function(Control) TypeOf Control Is Panel) _
    	.SelectMany(Function(Panel) Panel.Controls.OfType(Of Control)()) _
    	.Where(Function(Control) TypeOf Control Is RadioButton) _
    	.Cast(Of RadioButton)()
    
    In questa versione, si ottengono dai controlli del Form MyForm i controlli che sono di tipo Panel; da questi si recuperano tutti i controlli, tornando ad avere la stessa "enumerazione di enumerazione" del caso precedente, ossia un insieme di elementi per ogni pannello che contiene l'insieme di tutti i controlli (a prescindere dal tipo); l'uso di SelectMany() permette di appiattire queste enumerazioni combinandole in una sola, che viene infine filtrata per tenere in considerazione solo i controlli sono RadioButton; il cast finale permette di ottenere un enumerazione del tipo desiderato, RadioButton, rispetto al più generico tipo di partenza, che è Control.

    Anche in questo caso, l'espressione è solamente definita ma non eseguita fino a quando non si fa un For Each, oppure non si chiama First(), Last(), ToList(), ToArray() o un altro metodo equivalente.

    Ciao!
  • Re: IEnumerable risultato

    Questa mi è piaciuta!

    Riporto il codice che ho utilizzato, in sostituzione del precedente, dopo l'imbeccata di Alka che ringrazio, in modo che anche gli altri possano utilizzarla.
    Dim Query = From Control In TabControl2.Controls Where TypeOf Control Is TabPage
            Let TabPage = DirectCast(Control, TabPage)
            From Page In TabPage.Controls Where TypeOf Page Is Panel
            Let Pannello = DirectCast(Page, Panel)
            From Child In Pannello.Controls Where TypeOf Child Is RadioButton
            Select DirectCast(Child, RadioButton)
    
    Dim EnabledButton = From Item In Query.Cast(Of RadioButton)() Where Item.Enabled = True AndAlso Item.Text = "FAIL" AndAlso Item.Checked = True
            If EnabledButton.Count <> 0 AndAlso RadioButton84.Checked = True Then MsgBox("ATTENZIONE: Con un FAIL non selezionare questo campo.")
    
  • Re: IEnumerable risultato

    Grazie Alka.
    Ho letto con molta attenzione, LINQ riduce i controlli ad una sorta di database di controlli, molto potente nelle ricerche attraverso Query.
    Ora mi chiedo, quando è utile usare tale metodo?
    Qual è il vantaggio di "complicare" una cosa semplice come raggruppare i controlli in una lista e raggiungerli direttamente con un indice senza effettuare una ricerca?
    Usiamo un DB quando i dati da trattare sono variegati e corposi, centinaia di tipi e milioni di record, quando può capitare una cosa simile con dei controlli su un form?

    Avevo dato solo un'occhiata a LINQ, e questo thread mi ha fatto capire molte cose, ora so che LINQ deve essere tenuto in considerazione, mi manca solo il perché.
  • Re: IEnumerable risultato

    Rubik ha scritto:


    LINQ riduce i controlli ad una sorta di database di controlli, molto potente nelle ricerche attraverso Query.
    LINQ è solamente una libreria che aggiunge dei metodi di estensione a tutti i tipi che sono "enumerabili" (IEnumerable<T>).

    Ogni enumerazione estrae un valore alla volta partendo dal primo fino a esaurimento: i metodi LINQ consentono di concatenare filtri, ordinamenti, raggruppamenti, accorpamenti, somme, medie, conteggi, ecc. a qualcosa di enumerabile, creando una nuova enumerazione.

    Quando i valori vengono estratti dall'enumerazione uno alla volta, la catena dei metodi fa sì che ciascuna enumerazione creata chieda il valore successivo alla precedente, combinando sostanzialmente tutte le operazioni definite nel momento in cui l'espressione viene creata.

    Generando delle enumerazioni, i metodi non producono direttamente i risultati: questi vengono determinati quando si inizia l'estrazione dei loro valori, o perché impiegati in un ciclo (es. foreach() di C#) o usando uno dei tanti metodi che dall'enumerazione producono una lista, un array, il valore del primo elemento, dell'ultimo, dell'unico presente, ecc.

    Quello che è un ulteriore valore aggiunto di LINQ è il fatto di supportare anche una sintassi dedicata con cui è possibile esprimere la catena dei metodi in modo leggibile, e anche poter inviare l'espressione creata a diversi tipi di "motori", affinché analizzino l'albero dell'espressione e, oltre a lavorare su oggetti, possano applicarla anche a basi di dati (vedi Entity Framework) o altri interpreti che utilizzano l'espressione specificata per convertirla in una interrogazione in un altro linguaggio (es. SQL nel caso di EF) o per elaborarla in altro modo.

    Rubik ha scritto:


    Ora mi chiedo, quando è utile usare tale metodo?
    Va usato tutte le volte che è possibile.

    Rubik ha scritto:


    Qual è il vantaggio di "complicare" una cosa semplice come raggruppare i controlli in una lista e raggiungerli direttamente con un indice senza effettuare una ricerca?
    In realtà, la complicazione si ha evitando l'uso di LINQ. Supponiamo di avere una serie di controlli con la proprietà Visible e di voler ottenere una lista dei nomi di tutti i controlli che attualmente sono visibili. Come si farebbe generalmente? Io ipotizzo una cosa del genere...
    
    var visibleControlNames = new List<string>();
    
    foreach(var someControl in Form.Controls)
    {
      if (!someControl.Visible)
        continue;
        
      visibleControlNames.Add(someControl.Name);
    }
    
    Per sapere cosa fa questo codice, è necessario analizzarlo e, una volta viste le istruzioni, capire che alla fine nella lista vengono messi i nomi dei controlli quando questi risultano visibili.

    Usando LINQ, questa cosa si potrebbe riscrivere come segue:
    
      var visibleControlNames = (
          from c in Form.Controls
          where c.Visible
          select c.Name)
        .ToList();
    
    In questo caso, non è necessario analizzare il codice: basta leggerlo.

    Il codice esprime esattamente quella che è l'intenzione di ciò che si vuole ottenere.

    Certo, lo sforzo sembrerebbe superfluo, ma supponiamo ora di aggiungere una complessità, ossia ordinare i controlli in base al nome; ecco che il primo esempi richiede di scrivere del codice in più, e non poco, mentre con LINQ ce la possiamo cavare semplicemente aggiungendo la relativa clausola:
    
      var visibleControlNames = (
          from c in Form.Controls
          where c.Visible
          orderby c.Name
          select c.Name)
        .ToList();
    
    Possiamo estrarre dall'espressione quello che vogliamo, anche tipi anonimi. Ad esempio, se vogliamo ottenere un oggetto che contiene il riferimento al controllo e anche il suo nome, possiamo scrivere questo nella select (proiezione):
    
      var visibleControlNames = (
          from c in Form.Controls
          where c.Visible
          orderby c.Name
          select new {ControlName = c.Name, ControlObj = c})
        .ToList();
    
    Esistono poi tanti altri metodi di utilità: ad esempio, con Distinct() eliminiamo i duplicati, con Skip() e Take() rispettivamente saltiamo N elementi della sequenza e prendiamo un numero massimo di elementi da quella posizione in poi.

    Rubik ha scritto:


    Usiamo un DB quando i dati da trattare sono variegati e corposi, centinaia di tipi e milioni di record, quando può capitare una cosa simile con dei controlli su un form?
    Non c'entra il numero di elementi, bensì il tipo di operazione che si intende svolgere e la complessità necessaria per implementarla nel codice scrivendola manualmente al posto di usare gli operatori LINQ già disponibili: in questi casi, non fare uso di LINQ significa sprecare tempo e soprattutto scrivere codice "error prone" (i copia/incolla e la riproduzione di codice sempre uguale espone a errori).

    Rubik ha scritto:


    Avevo dato solo un'occhiata a LINQ, e questo thread mi ha fatto capire molte cose, ora so che LINQ deve essere tenuto in considerazione, mi manca solo il perché.
    Posso suggerire di scaricare un programma estremamente utile, LINQPad, e di vedere gli esempi che vi sono all'interno, facendo qualche esercitazione proposta dalla documentazione ufficiale.

    A mio avviso, conoscere LINQ è imprescindibile in .NET, soprattutto perché è una implementazione fatta molto bene che non esiste in tutti gli altri linguaggi dello stesso tipo, o almeno non in questa forma e con questi vantaggi.

    Ciao!
  • Re: IEnumerable risultato

    Grazie della delucidazione e correzione della mia definizione errata, dovuta al fatto che non avevo compreso bene il rapporto tra enumerabilità e i metodi aggiunti da LINQ.
  • Re: IEnumerable risultato

    Ciao,
    ho provato ad unire le due query, query e enabledbutton, in una unica riga. Ho cercato anche su internet ma probabilmente mi sfugge qualcosa.
    Come posso fare?

    Grazie.
  • Re: IEnumerable risultato

    KronS ha scritto:


    ho provato ad unire le due query, query e enabledbutton, in una unica riga.
    Ho cercato anche su internet ma probabilmente mi sfugge qualcosa.
    Come posso fare?
    Riporta sempre il codice che hai scritto, così da avere uno spunto di indagine.

    Ciao!
  • Re: IEnumerable risultato

    Il detto che le cose fatte in fretta non escono bene è sempre valido.
    Dim Query = From Control In TabControl2.Controls Where TypeOf Control Is TabPage
    Let TabPage = DirectCast(Control, TabPage)
    From Page In TabPage.Controls Where TypeOf Page Is Panel
    Let Pannello = DirectCast(Page, Panel)
    From Child In Pannello.Controls Where TypeOf Child Is RadioButton
    Select DirectCast(Child, RadioButton)

    Dim EnabledButton = From Item In Query.Cast(Of RadioButton)() Where Item.Enabled = True AndAlso Item.Text = "FAIL" AndAlso Item.Checked = True
    Queste sono le due righe. Effettivamente non cambia molto dimensionare una variabile in più ma credo mi sia molto utile capire come funziona Linq.

    Grazie.
  • Re: IEnumerable risultato

    KronS ha scritto:


    Queste sono le due righe. Effettivamente non cambia molto dimensionare una variabile in più ma credo mi sia molto utile capire come funziona Linq.
    La versione "su una sola riga" che hai scritto com'era?

    Attenzione che EnabledButton è di nuovo un nome fuorviante: il valore che avrai in quella variabile sarà non un pulsante, ma l'espressione che - quando eseguita - ti restituisce tutti i pulsanti (quindi una enumerazione di pulsanti, che potrebbe contenere anche un solo elemento) che soddisfano quel filtro (espresso con Where) applicato agli elementi che vengono restituiti dall'espressione che invece si trova in Query.

    Riassumendo, di fatto stai creando una nuova espressione che enumera e filtra il risultato di un'altra, e in EnabledButton salvi questo oggetto che rappresenta appunto l'espressione.

    Tutt'al più quindi, la variabile dovrebbe chiamarsi EnabledButtons per essere coerente con il risultato, poi ciò che manca è un ciclo o la chiamata a uno dei metodi che esegue l'espressione e restituisce il risultato nella forma desiderata (i soliti First(), Last(), ToList(), ecc.).

    Ragiona su questi aspetti e prova a riscrivere il codice.

    Ciao!
  • Re: IEnumerable risultato

    Forse mi sono espresso male.
    Dopo varie ricerche sono riuscito ad ottenere quello che cercavo:
    Dim Query = From Control In TabControl2.Controls Where TypeOf Control Is TabPage
    Let TabPage = DirectCast(Control, TabPage)
    From Page In TabPage.Controls Where TypeOf Page Is Panel
    Let Pannello = DirectCast(Page, Panel)
    From Child In Pannello.Controls.OfType(Of RadioButton)() Where Child.Checked = True And Child.Enabled = True And Child.Text = "FAIL"
    Select DirectCast(Child, RadioButton)
    Sembra funzionare.
    Il mio errore era quello di inserire dopo l'operatore
    Where
    il codice
    TypeOf Child Is RadioButton And Child.Checked = True .......
    che non funzionava.
    La riga NON FUNZIONANTE completa è questa:
    Dim Query = From Control In TabControl2.Controls Where TypeOf Control Is TabPage
    Let TabPage = DirectCast(Control, TabPage)
    From Page In TabPage.Controls Where TypeOf Page Is Panel
    Let Pannello = DirectCast(Page, Panel)
    From Child In Pannello.Controls Where TypeOf Child Is RadioButton and Child.Checked = True and ........
    Select DirectCast(Child, RadioButton)
    Adesso devo capire il perché.
  • Re: IEnumerable risultato

    KronS ha scritto:


    Il mio errore era quello di inserire dopo l'operatore
    Where
    il codice
    TypeOf Child Is RadioButton And Child.Checked = True .......
    che non funzionava.
    La riga NON FUNZIONANTE completa è questa:
    Dim Query = From Control In TabControl2.Controls Where TypeOf Control Is TabPage
    Let TabPage = DirectCast(Control, TabPage)
    From Page In TabPage.Controls Where TypeOf Page Is Panel
    Let Pannello = DirectCast(Page, Panel)
    From Child In Pannello.Controls Where TypeOf Child Is RadioButton and Child.Checked = True and ........
    Select DirectCast(Child, RadioButton)
    Adesso devo capire il perché.
    E' più semplice di quello che sembra.

    Se scrivi
    From Child In Pannello.Controls
    vuol dire che la variabile/segnaposto Child identificherà di volta in volta il singolo elemento presente nella Collection Pannello.Controls, e quindi sarà di tipo Control, ovvero la classe base da cui derivano tutti i controlli visuali.

    Nella clausola Where tu hai messo due condizioni logicamente connesse con l'operatore And:
    Where TypeOf Child Is RadioButton and Child.Checked = True
    La prima parte dell'espressione verifica se Child è in realtà un RadioButton, ed è corretta.
    La seconda parte invece non è corretta, perché a meno di eseguire un "cast", Child rimane di tipo Control e quindi non puoi scrivere Child.Checked dato che la proprietà Checked non è presente su tutti i controlli, ma solo sul RadioButton.

    In breve, se vuoi usare Child come RadioButton, devi fare un cast comunque, anche se prima hai verificato la sua effettiva appartenenza al tipo.

    Avresti potuto scrivere così:
    
    Where TypeOf Child Is RadioButton AndAlso DirectCast(Child, RadioButton).Checked = True
    
    L'uso dell'operatore AndAlso è obbligatorio: vedi questa spiegazione per dettagli.

    Ciao!
  • Re: IEnumerable risultato

    Alla fine ho dovuto seguire il tuo consiglio, Alka. Usando directcast diventa più semplice ma anche più agevole del mio. Sono riuscito così su una unica query a filtrare non solo i radio button ma anche I textbox.
    Devo trovare il tempo per leggere un libro su Linq altrimenti va a finire che questa discussione si protrarrà anche per tutto il 2021

    Grazie.
Devi accedere o registrarti per scrivere nel forum
14 risposte