Condividi tramite


Raccolte (Visual Basic)

Per molte applicazioni è utile creare e gestire gruppi di oggetti correlati. È possibile raggruppare gli oggetti in due modi: creando matrici di oggetti e creando raccolte di oggetti.

Le matrici sono estremamente utili per la creazione e l'uso di un numero fisso di oggetti fortemente tipizzati. Per altre informazioni sulle matrici, vedere Matrici.

Le raccolte consentono di lavorare in modo più flessibile con gruppi di oggetti. A differenza delle matrici, il gruppo di oggetti con cui si lavora può aumentare e diminuire in modo dinamico in base alle esigenze dell'applicazione. Per alcune raccolte è possibile assegnare una chiave a qualsiasi oggetto inserito nella raccolta in modo da recuperare rapidamente l'oggetto usando la chiave.

Una raccolta è una classe. Di conseguenza, prima di poter aggiungere elementi a una nuova raccolta è necessario dichiarare la raccolta.

Se la raccolta contiene elementi di un solo tipo di dati, è possibile usare una delle classi dello spazio dei nomi System.Collections.Generic. In una raccolta generica viene imposta l'indipendenza dai tipi, in modo da impedire che vengano aggiunti altri tipi di dati alla raccolta. Quando si recupera un elemento da una raccolta generica, non è necessario determinarne il tipo di dati né convertirlo.

Nota

Per gli esempi in questo argomento, includere le istruzioni Imports per gli spazi dei nomi System.Collections.Generic e System.Linq.

Uso di una raccolta semplice

Gli esempi in questa sezione usano la classe generica List<T>, che consente di usare un elenco di oggetti fortemente tipizzato.

L'esempio seguente crea un elenco di stringhe, quindi esegue l'iterazione nelle stringhe usando un'istruzione For Each...Next.

' Create a list of strings.
Dim salmons As New List(Of String)
salmons.Add("chinook")
salmons.Add("coho")
salmons.Add("pink")
salmons.Add("sockeye")

' Iterate through the list.
For Each salmon As String In salmons
    Console.Write(salmon & " ")
Next
'Output: chinook coho pink sockeye

Se il contenuto di una raccolta è noto in anticipo, si può usare un inizializzatore di raccolta per inizializzare la raccolta. Per altre informazioni, vedere Inizializzatori di insieme.

L'esempio seguente è identico all'esempio precedente, ma usa un inizializzatore di raccolta per aggiungere elementi alla raccolta.

' Create a list of strings by using a
' collection initializer.
Dim salmons As New List(Of String) From
    {"chinook", "coho", "pink", "sockeye"}

For Each salmon As String In salmons
    Console.Write(salmon & " ")
Next
'Output: chinook coho pink sockeye

È possibile usare un'istruzione For...Next anziché un'istruzione For Each per eseguire l'iterazione in una raccolta. Questo è possibile mediante l'accesso agli elementi della raccolta dalla posizione di indice. L'indice degli elementi inizia da 0 e termina in corrispondenza del numero di elementi meno 1.

Nell'esempio seguente l'iterazione negli elementi di una raccolta avviene mediante For…Next anziché mediante For Each.

Dim salmons As New List(Of String) From
    {"chinook", "coho", "pink", "sockeye"}

For index = 0 To salmons.Count - 1
    Console.Write(salmons(index) & " ")
Next
'Output: chinook coho pink sockeye

Nell'esempio seguente viene rimosso un elemento dalla raccolta specificando l'oggetto da rimuovere.

' Create a list of strings by using a
' collection initializer.
Dim salmons As New List(Of String) From
    {"chinook", "coho", "pink", "sockeye"}

' Remove an element in the list by specifying
' the object.
salmons.Remove("coho")

For Each salmon As String In salmons
    Console.Write(salmon & " ")
Next
'Output: chinook pink sockeye

Nell'esempio seguente vengono rimossi elementi da un elenco generico. Invece di un'istruzione For Each viene usata un'istruzione For...Next che esegue l'iterazione in ordine decrescente. Ciò è necessario perché il metodo RemoveAt fa sì che gli elementi dopo un elemento rimosso abbiano un valore di indice inferiore.

Dim numbers As New List(Of Integer) From
    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

' Remove odd numbers.
For index As Integer = numbers.Count - 1 To 0 Step -1
    If numbers(index) Mod 2 = 1 Then
        ' Remove the element by specifying
        ' the zero-based index in the list.
        numbers.RemoveAt(index)
    End If
Next

' Iterate through the list.
' A lambda expression is placed in the ForEach method
' of the List(T) object.
numbers.ForEach(
    Sub(number) Console.Write(number & " "))
' Output: 0 2 4 6 8

Per il tipo di elementi in List<T>, è possibile anche definire una classe personalizzata. Nell'esempio seguente la classe Galaxy viene usata dall'oggetto List<T> definito nel codice.

Private Sub IterateThroughList()
    Dim theGalaxies As New List(Of Galaxy) From
        {
            New Galaxy With {.Name = "Tadpole", .MegaLightYears = 400},
            New Galaxy With {.Name = "Pinwheel", .MegaLightYears = 25},
            New Galaxy With {.Name = "Milky Way", .MegaLightYears = 0},
            New Galaxy With {.Name = "Andromeda", .MegaLightYears = 3}
        }

    For Each theGalaxy In theGalaxies
        With theGalaxy
            Console.WriteLine(.Name & "  " & .MegaLightYears)
        End With
    Next

    ' Output:
    '  Tadpole  400
    '  Pinwheel  25
    '  Milky Way  0
    '  Andromeda  3
End Sub

Public Class Galaxy
    Public Property Name As String
    Public Property MegaLightYears As Integer
End Class

Tipi di raccolte

Molte raccolte comuni vengono fornite da .NET Framework. Ogni tipo di raccolta è progettato per uno scopo specifico.

In questa sezione sono descritte alcune classi di raccolte comuni:

Classi System.Collections.Generic

È possibile creare una raccolta generica usando una delle classi dello spazio dei nomi System.Collections.Generic. Una raccolta generica è utile quando ogni elemento al suo interno presenta lo stesso tipo di dati. Una raccolta generica applica la tipizzazione forte consentendo di aggiungere soltanto i tipi di dati desiderati.

La tabella seguente elenca alcune delle classi di uso frequente dello spazio dei nomi System.Collections.Generic:

Classe Descrizione
Dictionary<TKey,TValue> Rappresenta una raccolta di coppie chiave/valore organizzate in base alla chiave.
List<T> Rappresenta un elenco di oggetti accessibile in base all'indice. Fornisce metodi per la ricerca, l'ordinamento e la modifica degli elenchi.
Queue<T> Rappresenta una raccolta di oggetti FIFO (First-In First-Out).
SortedList<TKey,TValue> Rappresenta una raccolta di coppie chiave/valore ordinate per chiave in base all'implementazione IComparer<T> associata.
Stack<T> Rappresenta una raccolta di oggetti LIFO (Last-In First-Out).

Per altre informazioni, vedere Tipi di raccolte comunemente utilizzate, Selezione di una classe Collection e System.Collections.Generic.

Classi System.Collections.Concurrent

In .NET Framework 4 o versioni successive le raccolte dello spazio dei nomi System.Collections.Concurrent garantiscono operazioni thread-safe efficienti per accedere agli elementi della raccolta da più thread.

Le classi dello spazio dei nomi System.Collections.Concurrent devono essere usate in sostituzione dei tipi corrispondenti negli spazi dei nomi System.Collections.Generic e System.Collections ogni volta che più thread accedono contemporaneamente alla raccolta. Per altre informazioni, vedere Raccolte thread-safe e System.Collections.Concurrent.

Alcune classi incluse nello spazio dei nomi System.Collections.Concurrent sono BlockingCollection<T>, ConcurrentDictionary<TKey,TValue>, ConcurrentQueue<T> e ConcurrentStack<T>.

Classi System.Collections

Le classi dello spazio dei nomi System.Collections non archiviano gli elementi come oggetti tipizzati in modo specifico, ma come oggetti di tipo Object.

Quando possibile, usare le raccolte generiche degli spazi dei nomi System.Collections.Generic o System.Collections.Concurrent al posto dei tipi legacy dello spazio dei nomi System.Collections.

La tabella seguente elenca alcune classi di uso frequente nello spazio dei nomi System.Collections:

Classe Descrizione
ArrayList Rappresenta una matrice di oggetti le cui dimensioni sono incrementate in modo dinamico in base alle esigenze.
Hashtable Rappresenta una raccolta di coppie chiave/valore organizzate in base al codice hash della chiave.
Queue Rappresenta una raccolta di oggetti FIFO (First-In First-Out).
Stack Rappresenta una raccolta di oggetti LIFO (Last-In First-Out).

Lo spazio dei nomi System.Collections.Specialized offre classi di raccolte fortemente tipizzate e specializzate, ad esempio raccolte di sole stringhe, dizionari ibridi e dizionari a elenchi collegati.

Classe Collection di Visual Basic

È possibile usare la classe Collection di Visual Basic per accedere a un elemento della raccolta usando un indice numerico o una chiave String. Per aggiungere elementi a un oggetto raccolta, è possibile specificare o non specificare una chiave. Se si aggiunge un elemento senza una chiave, è necessario usare il relativo indice numerico per accedervi.

La classe Collection di Visual Basic archivia tutti gli elementi di tipo Object, pertanto è possibile aggiungere un elemento di qualsiasi tipo di dati. Non esiste alcuna misura per impedire l'aggiunta di tipi di dati non appropriati.

Quando si usa la classe Collection di Visual Basic, il primo elemento di una raccolta ha indice 1. Questo comportamento è diverso rispetto alle classi Collection di .NET Framework, per cui l'indice iniziale è 0.

Quando possibile, usare le raccolte generiche negli spazi dei nomi System.Collections.Generic o System.Collections.Concurrent al posto della classe Collection di Visual Basic.

Per ulteriori informazioni, vedere Collection.

Implementazione di una raccolta di coppie chiave/valore

La raccolta generica Dictionary<TKey,TValue> consente di accedere agli elementi di una raccolta usando la chiave di ogni elemento. Ogni aggiunta al dizionario è costituita da un valore e dalla chiave associata corrispondente. Il recupero di un valore tramite la relativa chiave è un'operazione veloce, perché la classe Dictionary viene implementata come tabella hash.

L'esempio seguente crea una raccolta Dictionary ed esegue l'iterazione nel dizionario usando un'istruzione For Each.

Private Sub IterateThroughDictionary()
    Dim elements As Dictionary(Of String, Element) = BuildDictionary()

    For Each kvp As KeyValuePair(Of String, Element) In elements
        Dim theElement As Element = kvp.Value

        Console.WriteLine("key: " & kvp.Key)
        With theElement
            Console.WriteLine("values: " & .Symbol & " " &
                .Name & " " & .AtomicNumber)
        End With
    Next
End Sub

Private Function BuildDictionary() As Dictionary(Of String, Element)
    Dim elements As New Dictionary(Of String, Element)

    AddToDictionary(elements, "K", "Potassium", 19)
    AddToDictionary(elements, "Ca", "Calcium", 20)
    AddToDictionary(elements, "Sc", "Scandium", 21)
    AddToDictionary(elements, "Ti", "Titanium", 22)

    Return elements
End Function

Private Sub AddToDictionary(ByVal elements As Dictionary(Of String, Element),
ByVal symbol As String, ByVal name As String, ByVal atomicNumber As Integer)
    Dim theElement As New Element

    theElement.Symbol = symbol
    theElement.Name = name
    theElement.AtomicNumber = atomicNumber

    elements.Add(Key:=theElement.Symbol, value:=theElement)
End Sub

Public Class Element
    Public Property Symbol As String
    Public Property Name As String
    Public Property AtomicNumber As Integer
End Class

Per usare invece un inizializzatore di raccolta per compilare la raccolta Dictionary, è possibile sostituire i metodi BuildDictionary e AddToDictionary con il metodo seguente.

Private Function BuildDictionary2() As Dictionary(Of String, Element)
    Return New Dictionary(Of String, Element) From
        {
            {"K", New Element With
                {.Symbol = "K", .Name = "Potassium", .AtomicNumber = 19}},
            {"Ca", New Element With
                {.Symbol = "Ca", .Name = "Calcium", .AtomicNumber = 20}},
            {"Sc", New Element With
                {.Symbol = "Sc", .Name = "Scandium", .AtomicNumber = 21}},
            {"Ti", New Element With
                {.Symbol = "Ti", .Name = "Titanium", .AtomicNumber = 22}}
        }
End Function

L'esempio seguente usa il metodo ContainsKey e la proprietà Item[] di Dictionary per trovare rapidamente un elemento in base alla chiave. La proprietà Item consente di accedere a un elemento nella raccolta elements usando il codice elements(symbol) in Visual Basic.

Private Sub FindInDictionary(ByVal symbol As String)
    Dim elements As Dictionary(Of String, Element) = BuildDictionary()

    If elements.ContainsKey(symbol) = False Then
        Console.WriteLine(symbol & " not found")
    Else
        Dim theElement = elements(symbol)
        Console.WriteLine("found: " & theElement.Name)
    End If
End Sub

L'esempio seguente usa invece il metodo TryGetValue per individuare rapidamente un elemento in base alla chiave.

Private Sub FindInDictionary2(ByVal symbol As String)
    Dim elements As Dictionary(Of String, Element) = BuildDictionary()

    Dim theElement As Element = Nothing
    If elements.TryGetValue(symbol, theElement) = False Then
        Console.WriteLine(symbol & " not found")
    Else
        Console.WriteLine("found: " & theElement.Name)
    End If
End Sub

Uso di LINQ per accedere a una raccolta

È possibile usare LINQ (Language-Integrated Query) per accedere alle raccolte. Le query LINQ forniscono funzionalità di filtro, ordinamento e raggruppamento. Per altre informazioni, vedere Introduzione a LINQ in Visual Basic.

Nell'esempio seguente viene eseguita una query LINQ su un oggetto List generico. La query LINQ restituisce una raccolta diversa che contiene i risultati.

Private Sub ShowLINQ()
    Dim elements As List(Of Element) = BuildList()

    ' LINQ Query.
    Dim subset = From theElement In elements
                  Where theElement.AtomicNumber < 22
                  Order By theElement.Name

    For Each theElement In subset
        Console.WriteLine(theElement.Name & " " & theElement.AtomicNumber)
    Next

    ' Output:
    '  Calcium 20
    '  Potassium 19
    '  Scandium 21
End Sub

Private Function BuildList() As List(Of Element)
    Return New List(Of Element) From
        {
            {New Element With
                {.Symbol = "K", .Name = "Potassium", .AtomicNumber = 19}},
            {New Element With
                {.Symbol = "Ca", .Name = "Calcium", .AtomicNumber = 20}},
            {New Element With
                {.Symbol = "Sc", .Name = "Scandium", .AtomicNumber = 21}},
            {New Element With
                {.Symbol = "Ti", .Name = "Titanium", .AtomicNumber = 22}}
        }
End Function

Public Class Element
    Public Property Symbol As String
    Public Property Name As String
    Public Property AtomicNumber As Integer
End Class

Ordinamento di una raccolta

L'esempio seguente illustra una procedura per ordinare una raccolta. Nell'esempio vengono ordinate le istanze della classe Car archiviate in un oggetto List<T>. La classe Car implementa l'interfaccia IComparable<T>, che richiede l'implementazione del metodo CompareTo.

Ogni chiamata al metodo CompareTo effettua un confronto unico che viene usato per l'ordinamento. Il codice scritto dall'utente presente nel metodo CompareTo restituisce un valore per ogni confronto dell'oggetto corrente con un altro oggetto. Il valore restituito è minore di zero se l'oggetto corrente è inferiore all'altro oggetto, maggiore di zero se l'oggetto corrente è superiore all'altro oggetto e zero se sono uguali. In questo modo è possibile definire nel codice i criteri di maggiore, minore e uguale.

Nel metodo ListCars l'istruzione cars.Sort() ordina l'elenco. Questa chiamata al metodo Sort di List<T> determina la chiamata automatica al metodo CompareTo per gli oggetti Car in List.

Public Sub ListCars()

    ' Create some new cars.
    Dim cars As New List(Of Car) From
    {
        New Car With {.Name = "car1", .Color = "blue", .Speed = 20},
        New Car With {.Name = "car2", .Color = "red", .Speed = 50},
        New Car With {.Name = "car3", .Color = "green", .Speed = 10},
        New Car With {.Name = "car4", .Color = "blue", .Speed = 50},
        New Car With {.Name = "car5", .Color = "blue", .Speed = 30},
        New Car With {.Name = "car6", .Color = "red", .Speed = 60},
        New Car With {.Name = "car7", .Color = "green", .Speed = 50}
    }

    ' Sort the cars by color alphabetically, and then by speed
    ' in descending order.
    cars.Sort()

    ' View all of the cars.
    For Each thisCar As Car In cars
        Console.Write(thisCar.Color.PadRight(5) & " ")
        Console.Write(thisCar.Speed.ToString & " ")
        Console.Write(thisCar.Name)
        Console.WriteLine()
    Next

    ' Output:
    '  blue  50 car4
    '  blue  30 car5
    '  blue  20 car1
    '  green 50 car7
    '  green 10 car3
    '  red   60 car6
    '  red   50 car2
End Sub

Public Class Car
    Implements IComparable(Of Car)

    Public Property Name As String
    Public Property Speed As Integer
    Public Property Color As String

    Public Function CompareTo(ByVal other As Car) As Integer _
        Implements System.IComparable(Of Car).CompareTo
        ' A call to this method makes a single comparison that is
        ' used for sorting.

        ' Determine the relative order of the objects being compared.
        ' Sort by color alphabetically, and then by speed in
        ' descending order.

        ' Compare the colors.
        Dim compare As Integer
        compare = String.Compare(Me.Color, other.Color, True)

        ' If the colors are the same, compare the speeds.
        If compare = 0 Then
            compare = Me.Speed.CompareTo(other.Speed)

            ' Use descending order for speed.
            compare = -compare
        End If

        Return compare
    End Function
End Class

Definizione di una raccolta personalizzata

È possibile definire una raccolta implementando l'interfaccia IEnumerable<T> o IEnumerable. Per altre informazioni, vedere Enumerazione di una raccolta.

Sebbene sia possibile definire una raccolta personalizzata, in genere è preferibile usare le raccolte incluse in .NET Framework, descritte in Tipi di raccolte in precedenza in questo argomento.

L'esempio seguente definisce una classe di raccolte personalizzata denominata AllColors. Questa classe implementa l'interfaccia IEnumerable che richiede l'implementazione del metodo GetEnumerator.

Il metodo GetEnumerator restituisce un'istanza della classe ColorEnumerator. ColorEnumerator implementa l'interfaccia IEnumerator che richiede l'implementazione della proprietà Current e dei metodi MoveNext e Reset.

Public Sub ListColors()
    Dim colors As New AllColors()

    For Each theColor As Color In colors
        Console.Write(theColor.Name & " ")
    Next
    Console.WriteLine()
    ' Output: red blue green
End Sub

' Collection class.
Public Class AllColors
    Implements System.Collections.IEnumerable

    Private _colors() As Color =
    {
        New Color With {.Name = "red"},
        New Color With {.Name = "blue"},
        New Color With {.Name = "green"}
    }

    Public Function GetEnumerator() As System.Collections.IEnumerator _
        Implements System.Collections.IEnumerable.GetEnumerator

        Return New ColorEnumerator(_colors)

        ' Instead of creating a custom enumerator, you could
        ' use the GetEnumerator of the array.
        'Return _colors.GetEnumerator
    End Function

    ' Custom enumerator.
    Private Class ColorEnumerator
        Implements System.Collections.IEnumerator

        Private _colors() As Color
        Private _position As Integer = -1

        Public Sub New(ByVal colors() As Color)
            _colors = colors
        End Sub

        Public ReadOnly Property Current() As Object _
            Implements System.Collections.IEnumerator.Current
            Get
                Return _colors(_position)
            End Get
        End Property

        Public Function MoveNext() As Boolean _
            Implements System.Collections.IEnumerator.MoveNext
            _position += 1
            Return (_position < _colors.Length)
        End Function

        Public Sub Reset() Implements System.Collections.IEnumerator.Reset
            _position = -1
        End Sub
    End Class
End Class

' Element class.
Public Class Color
    Public Property Name As String
End Class

Iteratori

Un iteratore viene usato per eseguire un'iterazione personalizzata in una raccolta. Un iteratore può essere un metodo o una funzione di accesso get. Un iteratore usa un'istruzione Yield per restituire ogni elemento della raccolta, uno alla volta.

Per chiamare un iteratore usare un'istruzione For Each...Next. Ogni iterazione del ciclo For Each chiama l'iteratore. Quando si raggiunge un'istruzione Yield nell'iteratore, viene restituita un'espressione e viene mantenuta la posizione corrente nel codice. L'esecuzione viene ripresa a partire da quella posizione la volta successiva che viene chiamato l'iteratore.

Per altre informazioni, vedere Iteratori (Visual Basic).

Nell'esempio seguente viene usato un metodo iteratore. Il metodo iteratore dispone di un'istruzione Yield all'interno di un ciclo For...Next. Nel metodo ListEvenNumbers ogni iterazione del corpo dell'istruzione For Each crea una chiamata al metodo iteratore, che procede all'istruzione Yield successiva.

Public Sub ListEvenNumbers()
    For Each number As Integer In EvenSequence(5, 18)
        Console.Write(number & " ")
    Next
    Console.WriteLine()
    ' Output: 6 8 10 12 14 16 18
End Sub

Private Iterator Function EvenSequence(
ByVal firstNumber As Integer, ByVal lastNumber As Integer) _
As IEnumerable(Of Integer)

' Yield even numbers in the range.
    For number = firstNumber To lastNumber
        If number Mod 2 = 0 Then
            Yield number
        End If
    Next
End Function

Vedi anche