Jason. Cała informatyka w jednym miejscu!

Dowiedzieliśmy się jak powstaje klasa w "CSharpie". Zaprezentuję Wam teraz drugi rodzaj "bytu". Słyszeliście o czymś takim jak "struktura" :)? Dzięki słowu kluczowemu "struct" w języku C#, tworzycie sobie nowy programistyczny byt, na takiej samej zasadzie jak tworzenie obiektu opartego na klasie. Działa on jednak inaczej w porównaniu do klasy zarówno w kwestii architektury, jak i sposobu działania. Moim zadaniem jest wszyściutko rzetelnie przedstawić, a Was poproszę jak zwykle o atencję :P.

"STRUCT" W JĘZYKU C#. NAWIĄZANIE DO STARSZEGO BRATA

"struct" nie jest niczym unikatowym w tym języku, bowiem znany jest dobrze z języka C istniejącego już pięćdziesiąt lat! Natomiast został on w pewnym stopniu "odświeżony" i nieco bardziej zadbany. Struktura to niestandardowy typ wartościowy (nazywany zamiennie typem bezpośrednim). Została ona skonstruowana w sposób do złudzenia przypominający klasę od strony samych pól (danych składowych), choć BARDZO różni się od klasy wieloma szczegółami. Kolokwialnie rzecz ujmując, chodzi o przechowywanie wielu różnych wartości o różnych typach danych w jednym miejscu, takim jakby "kontenerze".

Według opinii wielu biegłych programistów, struktury powinny być wykorzystywane dla niewielkiej liczby danych, a same dane powinny być niemodyfikowalne (więcej szczegółów w dalszej części, bo to wymaga gruntownego wytłumaczenia :O). Często za przykład zastosowania podaje się punkt w układzie współrzędnych. Posiada wartości osi X, Y i Z określających położenie w przestrzeni (które jak najbardziej są ze sobą powiązane) i dużo lepiej wygląda opakowanie je w jakiś wspólny budulec, niż żeby trzy wartości były porozwalane w różnych miejscach kodu (trzy zmienne istniejące "na zewnątrz").

PRZYKŁAD KODU ŹRÓDŁOWEGO

Oprę się jak zwykle o fragmenty kodów źródłowych, żeby już teraz Was stopniowo przyzwyczajać. "struct" w języku C# definiuje się identycznie jak klasę. Jedyną różnicą jest słowo kluczowe "struct", które zastępuje "class" ale reszta pozostaje bez zmian:

struct MyStruct
{
	public int x;
}

Czy to wszystko? Tak, to wszystko :D! Wewnątrz klamerek umieszczacie tyle danych, ile chcecie, natomiast nalegam żeby zachować jakiś umiar, bo przy dużej liczbie danych to bardziej będzie pasować klasa (choć to niejedyny czynnik decydujący o tym, co powinno się wybrać).

DWA SPOSOBY WYKORZYSTYWANIA STRUKTURY

Teraz przyjrzymy się tworzeniem instancji na podstawie struktury. I tu się robi wyjątkowo, gdyż możemy pójść dwiema ścieżkami, a obie przynoszą różne konsekwencje w skutkach.

INSTANCJA BEZ WYWOŁANIA KONSTRUKTORA

Najpierw rzecz, która może nie mieścić się w głowie. "struct" w języku C# nie wymaga używania konstruktora celem korzystania ze struktury w innych miejscach kodu :O! Odpowiedzią na pytanie "dlaczego?" jest fakt, iż jest to typ bezpośredni. Liczba całkowita także należy do typów bezpośrednich. I co, wstawiając liczby musieliście kiedykolwiek wywoływać na nich konstruktor :D? No właśnie.

Możemy sobie wstawić samą deklarację struktury gdziekolwiek:

MyStruct ms;

i korzystać z umieszczonych w środku pól, tak jak to robiliśmy podczas zaznajamiania się z obiektami i klasami:

ms.x = 45;

Kompilator nie będzie miał żadnych pretensji. Natomiast macie obowiązek takiego zainicjowania każdej danej składowej ręcznie, gdyż wszystkie zmienne pozostaną niezainicjowane. Zakładam, że pamiętacie jak opowiadałem w jakiej sytuacji zmienne otrzymują wartości domyślne:

Console.WriteLine(ms.x);	// błąd, jeśli polu "x" nie przypisano żadnej wartości

W dalszej części artykułu wytłumaczę dlaczego korzystanie ze struktury bez inicjalizacji jest dopuszczalne.

INSTANCJA Z WYWOŁANIEM KONSTRUKTORA

Alternatywą jest "obiektowe" podejście do sprawy i wywołanie konstruktora struktury przy pomocy instrukcji "new" z nawiasami:

MyStruct ms = new MyStruct();

Efekt? Wszystkie, ale to absolutnie WSZYSTKIE pola struktury będą posiadały wartość domyślną, chyba że dodamy sobie konstruktor a w nim będą poszczególne przypisania, co byłoby "mile widziane" w strukturze:

public MyStruct(int x)
{
	this.x = x;
}

Skoro dotarliśmy do konstruktorów, to lepiej żebyście się o tym dowiedzieli ode mnie.

MOŻESZ ZDEFINIOWAĆ NOWY KONSTRUKTOR, ALE JEST JEDEN WARUNEK!

Nie może to być konstruktor bezparametrowy. Kiedy przejdzie nam przez myśl dodać konstruktor z pustą listą parametrów formalnych, wyskoczy błąd kompilacji!

public MyStruct
{
	public MyStruct()
	{
		// błąd kompilacji!
	}
}

Ale...dlaczego :(? A dlatego, że "struct" w języku C# musi posiadać swoją niejawną wersję konstruktora bezparametrowego. Gdybyście nie wiedzieli, kompilator zawsze "dorzuca od siebie" domyślny konstruktor bez żadnych instrukcji wewnątrz, w sytuacji kiedy nie zostanie żadny wprowadzony jawnie. To samo dotyczy także klas i to samo występuje w innych językach wysokiego poziomu (np. Java). "struct" posiada własny konstruktor bezparametrowy, który został zaprogramowany na okoliczność przypisania wszystkim polom wartości domyślnych. Rekapitulując, wolno Wam dodawać konstruktory, ale tylko z listą parametrów:

public MyStruct
{
	public int x;
	
	public MyStruct(int x)
	{
		// dopuszczalne
	}
}

JAKIŚ KOMENTARZ W SPRAWIE METOD?

Byłbym zapomniał! W C# struktury mają prawo dysponować własnymi metodami i tym się różni "struct" w języku C# od "struct" w języku C. W języku C kompilator Was "wyśmieje", w C# zostanie to zaakceptowane:

struct MyStruct
{
	public int Square(int x) => x*x;
}

Sugeruję, żeby metody nie były implementowane w celu modyfikacji danych składowych! Polecam bardziej je ukierunkować w stronę zwracania wyników obliczeń, a jeśli musi dojść do edycji, to lepiej będzie zaprogramować zwrot nowej instancji struktury przez metodę.

Dodam od siebie na marginesie - właściwości też jak najbardziej mogą się znaleźć w strukturze.

STRUKTURA JEST PRZEKAZYWANA DO FUNKCJI JAKO WARTOŚĆ!

To kolejna różnica pomiędzy klasą, a strukturą o której po prostu musicie wiedzieć! Sytuacja, w której dowolna metoda występująca poza naszą strukturą przyjmuje za parametr obiekt typu referencyjnego:

void DoSomething(MyClass mc)
{
	// modyfikacje zostaną zachowane po zrealizowaniu poleceń
}

to już wiemy, że odbywa się przekazywanie przez referencję i wszelkie modyfikacje danych składowych zostaną utrwalone po zakończeniu instrukcji. Natomiast kiedy w grę wchodzi "struct" w języku C#, to wtedy jest przekazywanie przez kopię! To jest kolejna cecha typów bezpośrednich, już nie samej struktury.

PRZECZYTAJ TO!

Jeszcze jedno. Od książek po dokumentację będzie zalecenie, żeby raz podane wartości były niemodyfikowalne (warto znać "readonly"). Rekomendacje będą zależne od wersji C# jaką posiadacie zainstalowaną.

7.2+

Jeżeli dysponujecie C# w wersji 7.2 lub wyższej, będzie sugestia, żeby do nagłówka struktury dodać modyfikator tylko do odczytu, "readonly". I tyle:

readonly struct MyStruct
{
	
}
6.0+

Posiadacie C# 6.0? To macie możliwość wprowadzenia automatycznej implementacji właściwości dla wszystkich występujących pól, co w tym przypadku będzie stanowiło "najgorętszą" rekomendację:

public int X {get;}
<6.0

Ci, którzy mają wersję niższą od "szóstki", pozostaje Wam jedynie obejście w postaci właściwości o publicznym dostępie, która "zwraca się" w stronę prywatnego pola tylko do odczytu ("readonly"):

public int X
{
	get
	{
		return x;
	}
}

private readonly int x;

We wszystkich wyżej wymienionych przypadkach występuje założenie, że zdefiniowaliście sobie własny konstruktor z listą parametrów formalnych, który ma na celu przypisanie wartości polom.

Powtarzam. Cały czas to są tylko ZA-LE-CE-NIA! W żadnym z wyżej wymienionych przypadków standard języka nie nakłada wymagań, że "struct" w języku C# ma mieć wyżej wymienione czynniki. Wszędzie będziecie mieli wyraźnie zaznaczone, że wyłącznie tak POWINNO wyglądać przez wzgląd na naturę jaką kierują się typy bezpośrednie. Ich się (z reguły) nie modyfikuje, tylko tworzy się nowe instancje w oparciu o istniejące. Kiedy dodajecie do siebie dwie stałe liczby całkowite (np. 5 + 7), to obchodzi Was co się później stanie z tymi składnikami? Nie, bo dzięki zsumowaniu obu składników powstaje nowa instancja typu bezpośredniego (12)! Wartości typów bezpośrednich są jedynie pobierane ("getter"), ale nie powinny być modyfikowane. Tak jest z liczbami, ze znakami, z wartościami logicznymi i tak samo ze strukturami, ale ciągle piszę jak szalony, że to tylko ZALECENIE.

Rozdział uznałem za tak wyjątkowo ważny, że postanowiłem zrobić z tego osobny akapit rzucający się w oczy ;).

STRUKTURA VS. KLASA

Ponieważ występuje wiele podobieństw do klasy, specjalnie wstawiłem na zakończenie tej przygody tabelkę porównawczą, żeby stanowiła streszczenie najważniejszych informacji. Uprzedzam lojalnie, że to mogą nie być wszystkie różnice jakie są mi znane:

KLASA STRUKTURA
słowo kluczowe "class" słowo kluczowe "struct"
jest to typ referencyjny jest to typ wartościowy (bezpośredni)
obiekt jest alokowany na stercie struktura jest alokowana na stosie
wymaga użycia konstruktora celem zainicjowania nie wymaga użycia konstruktora, choć po jego użyciu wszystkie pola otrzymują wartość domyślną; w przeciwnym razie wszystkie są niezainicjowane
domyślny konstruktor bezparametrowy może zostać przesłonięty domyślny konstruktor bezparametrowy musi pozostać nietknięty
pola mogą zostać zainicjowane w momencie deklaracji wewnątrz klasy pola nie mogą zostać zainicjowane w momencie deklaracji wewnątrz struktury
jako parametr funkcji, obiekt przekazywany jest przez referencję jako parametr funkcji, struktura przekazywana jest jako kopia
pola mogą być jak najbardziej modyfikowane pola powinny być tylko do odczytu
najbardziej użyteczna dla dużej liczby danych składowych i metod najbardziej użyteczna dla niewielkiej liczby danych składowych

Struktura w języku C#

Konstrukcja "struct" w języku C# tworzy strukturalny typ danych (bezpośredni), który charakteryzuje się przekazywaniem wartości przez kopię. Ze względów "natury" typu bezpośredniego, powinien on podlegać ochronie modyfikacji danych składowych.


Artykuł po raz kolejny wyszedł mi bardzo długi :(. Nie ma to jak darmowa biblia wiedzy na temat struktur ;). Nie zastąpi porządnej literatury (zresztą, nawet nie próbuję), natomiast mam nadzieję że komuś się bardzo przyda podczas nauki "CSharpa", a nie wyda na edukację ani grosza :D.

PODOBNE ARTYKUŁY