Kolekce   otázka

VB.NET, .NET

Jak pracovat s kolekcemi? Nedaří se mi najít nějaký slušný příklad. K čemu přesně jsou? Asi jsem nepochopil princip, výsledek je jiný než jsem čekal.

Mam pokusný formulář, s textboxy a dalšími prvky. Některé jsem nacpal do kolekce:

Private Jmena As New Microsoft.VisualBasic.Collection()

Jmena.Add(TextBoxJmeno1.Text)
Jmena.Add(TextBoxJmeno2.Text)
atd

Teď tam chci zapsat data:

	Dim Text As String
	For Each Text In Jmena
		Text = "Pokus"
	Next

Ale TextBoxy jsou prázdné.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Kolekce jsou velmi šikovný nástroj pro správu většího množství dat stejného druhu. Pro příklad, který jste uvedl je ale nutné pochopit, jaký je rozdíl mezi referencí a hodnotou.

REFERENCE

Public Sub Test()
    Dim a = New TextBox()
    Dim b = New TextBox()
    ' přiřadíme do a REFERENCI b (na a už nic neodkazuje, zapomene se, smaže ho Garbage Collector)
    a = b (a = ref. na 2. TB, b = ref. na 2. TB)
    ' změníme a
    a.Text = "test" (a.Text = "test", b.Text = "test")
    ' změna se promítne do b
    ' protože obě proměnné ukazovali na jeden objekt (a = b)
End Sub

Co je to Garbage Collector a vůbec detailnější popis nabízí tento web v seriálu článků o základech VB .NET.

HODNOTA

Public Sub Test()
    Dim a = 1
    Dim b = 2
    ' přiřadíme do a HODNOTU b
    a = b (a = 2, b = 2)
    ' změníme a
    a = 4 (a = 4, b = 2)
    ' změna se nepromítne do b
    ' obě proměnné měli svou hodnotu
End Sub

Reference je v podstatě celé číslo (32 nebo 64bitové, záleží na systému), které ukazuje na nějaké místo na haldě (ang. Heap), což je místo, kam si CLR Runtime (běhové prostředí .NET Frameworku) ukládá referenční typy a reference jsou odkazy na to místo, které vedou na konkrétní objekty.

To znamená, že pokud uděláte kolekci hodnotových typů (Integer, Double, atd.) a změníte jeden prvek, v ostatních se to neprojeví (změníte hodnotu).

Pokud uděláte kolekci referenčních typů (tříd, jako například TextBox):

Dim list = New System.Collections.Generic.List(Of TextBox)
Dim tb1 = new TextBox()
Dim tb2 = New TextBox() With {.Text = "bla"}
list.Add(tb1)
list.Add(tb1)
list.Add(tb2)

A takto jí naplníte (máte tam dvě reference na první TextBox a jednu reference na druhý TextBox), budou jejich vlastnosti vypadat takto:

list(0).Text = ""
list(1).Text = ""
list(2).Text = "bla"

Pokud změníte objekt na který odkazuje první (nebo druhá, jsou stejné) reference:

list(0).Text = "test?"

Změny se narozdíl od hodnotových typů promítnou na referencovaném objektu a všechny reference je budou reflektovat:

list(0).Text = "test?"
list(1).Text = "test?"
list(2).Text = "bla"

Nyní přichází ta důležitá část. String je sice referenční typ, ale pracuje velmi zvláštně. Je Immutable, což znamená, že pokud ho změníte, vytvoří se jeho nová Instance, tedy nová reference na nový objekt String. V praxi to tedy působí, jako by byl hodnotový. Muttable (měnitelná) verze Stringu je System.Text.StringBuilder, který zachovává pořád stejnou referenci při jeho změnách a až na konečné zavolání .ToString() vrací finální String, který je už opět Immutable.

Tomáš Herceg má na svém blogu hezký článek o tom, proč se obzvláště u větších (delších) textů vyplatí používat StringBuilder namísto String.

http://www.vbnet.cz/blog-clanek--355-pro...

Není tedy dobrý nápad v kolekci odkazovat na TextBox.Text, ale přímo na TextBox:

Public Sub Form1_Load(...) Handles MyBase.Load
    Me.Controls.Add(New TextBox())
    Me.Controls.Add(New TextBox())
    Me.Controls.Add(New TextBox())
    Dim kolekce = Me.Controls.OfType(Of TextBox)()
    ' toto volání vrátí kolekci prvků na formuláři, jež jsou typu TextBox
    kolekce(0).Text = "test?"
End Sub

Po spuštění formuláře by se měl Text prvního TextBoxu změnit na "test?".

Celé kouzlu spočívá v tom, že s kolekcemi se vyhnete tomuto:

Dim proměnná1 = 1
Dim proměnní2 = 2
Dim proměnná3 = 3

Prostě to nahradíte kolekcí:

Dim proměnné = New List(Of Integer)({1,2,3})
' nebo
Dim proměnné = New List(Of Integer) From {1,2,3}
' From klauzule automaticky volá .Add proceduru s prvky v poli {}

Existují také další typy kolekcí, jako Stack(Of T), která funguje tak, že první prvek, který tam vložíte, jako poslední zase dostanete (LIFO last-in-first-out), takže:

Dim stack As New Stack(Of Integer)
stack.Push(1)
stack.Push(2)
stack.Pop() ' 2
stack.Pop() ' 1

Tato kolekce je velmi užitečná při práci s rekurzí.

Nebo Queue(Of T) (FIFO first-in-first-out), která funguje tak, že první vložený prvek je první výstupní prvek:

Dim queue As New Queue(Of Integer)
queue.Enqueue(1)
queue.Enqueue(2)
queue.Dequeue() ' 1
queue.Dequeue() ' 2

A v neposlední řadě, slovník Dictionary(Of TKey, TValue), kolekce klíč hodnota (unikátní klíč):

Dim dictionary As New Dictionary(Of String, Person)
dictionary.Add("Alex Clarai", New Person("Alex", "Clarai"))
dictionary("Alex Clarai").Birthdate = Date.Now()

Slovník je velmi rychlá záležitost, protože klíč interně hashuje, takže vyhledávání v něm probíhá rychlostí blesku.

Mimochodem i se slovníkem je možné využívat From klauzuli, stačí mít na paměti, že potřebujeme slovník plnit páry klíč-hodnota:

Dim dictionary As New Dictionary(Of Integer, String) From {{1,"jedna"},{2,"dvě"}}

Snad jsem to popsal srozumitelně, pro pochopení rozdílu mezi hodnotou a referencí a hlavně pro pochopení, k čemu je to dobré a kdy to bude jak fungovat, je třeba dost praxe s programování. Pokud si zvyknete kolekce používat, uvidíte, že Vám v budoucnu budou velmi k dobru.

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Dobrý den,

velmi pěkný elaborát. Možná by stálo za to z něj rovnou udělat článek na web :-)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Rozdíl mezi referenci a hodnotu chápu. Ale celkem jsem z toho zmaten. Proč je tam "New"? To by přece měolo vytvořit úplně nový objekt. Očekával bych spíše ByRef jako u funkcí.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Používal jsem skutečně nové instance TextBoxů, ovšem pouze pro demonstrační účely. Přesná odpověď na Vaši otázku, kdy již máte na formuláři hotové rozhraní:

Dim textBoxes As New List(Of TextBox)
Public Sub Form1_Load(...) Handles MyBase.Load
  textBoxes.Add(TextBoxJmeno1) ' reference na TextBoxJmeno1
  textBoxes.Add(TextBoxJmeno2) ' reference na TextBoxJmeno2
  ' ...
End Sub
Public Sub Button1_Click(...) Handles Button1.Click
  For Each textBox In list
    ' přistupujeme na vlastnost Text referencovaného objektu
    textBox.Text = "test"
  Next
End Sub

Používám třídu System.Collections.Generic.List(Of T) namísto Microsoft.VisualBasic.Collection, protože List(Of T) je silně typový. Do Collection přidáváte Object a dostáváte Object.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Díky už je mi to jasné

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Hezká odpověď, jak dlouho jste ji vypracovával? :-D

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Párkrát jsem to editoval, opravoval gramatiku, přidal formátování, a odkaz, ale zbytek je psaný z hlavy a přepsat něco z hlavy "na papír" je otázka desítek sekund :-)

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback