Struktury
Stronę tą wyświetlono już: 2007 razy
Wstęp
Struktury w C# są typami danych, które przechowują wartości pól w nich zawartych. Dla odróżnienia klasy w C# przechowują wskaźnik na miejsce, gdzie dane pól klasy się znajdują. Do typów, które przechowują wartość należą również: char, byte, int, uint, short, ushort, float, double, decimal. Co to w praktyce oznacza pokażę nieco później.
Struktury dziedziczą (jak zresztą wszystko inne) po klasie Object, nie mogą one dziedziczyć po żadnych innych klasach, czy strukturach jak również nie mogą być dziedziczone, mogą one jednak dziedziczyć po dowolnej liczbie interfejsów. Ważną cechą jest również brak destruktora, lecz każda struktura może posiadać konstruktor i metody wewnętrzne.
Deklaracja struktury w C#
Do utworzenia struktury C# służy słowo kluczowe struct. Oto przykładowy ogólny model deklaracji struktury:
- rodzaj_dostępu struct nazwa_struktury : interfejs1, interfejs2, .... // dziedziczenie po interfejsach
- {
- rodzaj_dostępu typ_danych nazwa_pola1;
- rodzaj_dostępu typ_danych nazwa_pola2;
- rodzaj_dostępu nazwa_struktury(typ_danych _nazwa_pola1, typ_danych _nazwa_pola2) // konstruktor
- {
- nazwa_pola1 = _nazwa_pola1;
- nazwa_pola2 = _nazwa_pola2;
- }
- }
Utworzenie instancji struktury odbywa się w następujący sposób:
- var str = new nazwa_struktury();
Powyższe wywołanie spowoduje utworzenie instancji struktury z jednoczesnym wywołaniem jej konstruktora.
Rodzaje dostępu
W C# mamy do dyspozycji pięć różnych typów dostępu:
- public - oznacza, że dana zmienna lub metoda są dostępne z każdego miejsca kodu. Ten typ dostępu jest domyślny dla interfejsów;
- private - oznacza, że dana zmienna lub metoda są dostępne tylko z wnętrza danego typu, w którym została zadeklarowana. Ten typ dostępu jest domyślny dla klas i struktur;
- protected - oznacza, że dana zmienna lub metoda są dostępne z wnętrza klasy do której należy oraz z wnętrza klas pochodnych;
- internal - oznacza, że dana zmienna lub metoda są dostępne jedynie z wnętrza złożenia. C# kompiluje pliki *.cs w moduły a następnie grupuje w złożenia;
- protected internal - oznacza, że dana zmienna lub metoda są dostępne z wnętrza klasy, w której została zadeklarowana lub z wnętrza złożenia.
Przykład praktyczny implementacji struktury
Oto mały programik, w którym zaszyłem implementację struktury rectangle:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace Struktury
- {
- class Program
- {
- // ############################ STRUKTORA RECTANGLE ###############################
- public struct rectangle
- {
- // Domyślnie prywatne pola klasy
- int left;
- int right;
- int top;
- int bottom;
- // Publiczny konstruktora
- public rectangle(int left, int right, int top, int bottom)
- {
- this.left = left;
- this.right = right;
- this.top = top;
- this.bottom = bottom;
- }
- // Publiczna metoda struktury sprawdzająca, czy dany punkt znajduje się wewnątrz prostokąta
- public bool PtInRect(int x, int y)
- {
- return x >= left && x <= right && y >= top && y <= bottom;
- }
- // Publiczna metoda statyczna struktury, sprawdzająca czy dany punkt mieści się w podanym prostokącie
- public static bool PtInRect(int x, int y, int left, int right, int top, int bottom)
- {
- return x >= left && x <= right && y >= top && y <= bottom;
- }
- public override string ToString() // Przeładowanie standardowej metody ToString, która siedzi przyczajona niczym wojownik ninja w klasie bazowej Object
- {
- return "Prostokąt: lewy = " + left + "; prawy = " + right + "; górny = " + top + "; dolny = " + bottom;
- }
- }
- // ############################# MAIN #############################
- static void Main(string[] args)
- {
- rectangle r = new rectangle(10, 100, 10, 200);
- int x = 0;
- int y = 0;
- Console.Write("Podaj współrzędną x: ");
- try
- {
- x = int.Parse(Console.ReadLine());
- }
- catch { }
- Console.Write("Podaj współrzędną y: ");
- try
- {
- y = int.Parse(Console.ReadLine());
- }
- catch { }
- if (r.PtInRect(x, y)) // wywołanie metody wewnętrznej instancji struktury rectangle
- {
- Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; znajduje się wewnątrz prostokąta " + r.ToString());
- }
- else
- {
- Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; nie znajduje się wewnątrz prostokąta " + r.ToString());
- }
- int left = 0;
- int right = 50;
- int top = 0;
- int bottom = 50;
- if (rectangle.PtInRect(x, y, left, right, top, bottom)) // wywołanie metody statycznej struktury rectangle
- {
- Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; znajduje się wewnątrz prostokąta lewy = " + left + "; prawy = " + right + "; górny = " + top + "; dolny = " + bottom);
- }
- else
- {
- Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; nie znajduje się wewnątrz prostokąta lewy = " + left + "; prawy = " + right + "; górny = " + top + "; dolny = " + bottom);
- }
- Console.ReadLine();
- }
- }
- }
Wewnątrz struktury rectangle utworzone zostały cztery pola typu int. Pola te mają domyślnie przypisany dostęp private, co jak już wcześniej wspominałem oznacza, że nie ma do nich dostępu z zewnątrz. Sam konstruktor tejże struktury przyjmuje cztery argumenty, które odpowiadają nazwom pól struktury rectangle. Aby możliwe było ustawienie pól struktury wewnątrz jej konstruktora użyte zostało słowo kluczowe this, oznaczający wskaźnik na instancję struktury rectangle.
Wewnątrz struktury rectangle utworzone zostały również dwie metody publiczne o nazwie PtInRect, których celem jest stwierdzenie, czy podany punkt znajduje się w prostokącie. Istnieje tutaj wersja tej metody ściśle powiązana z instancją struktury rectangle, która przyjmuje jako parametry jedynie współrzędną x i y. Tę metodę można wywoływać bezpośrednio jedynie wewnątrz metod nie statycznych struktury rectangle czy w jej konstruktorze, zaś pośrednio można ją wywołać z poziomu instancji tejże struktury. Druga wersja jest typu statycznego static i nie jest powiązana z żadną instancją struktury w związku z czym można ją wywołać w dowolnym miejscu z poziomu nazwy tejże struktury.
Ostatnia metoda ToString (również publiczna) jest metodą przeciążającą override metodę podstawową klasy Object. Metoda ta zamienia dane zawarte wewnątrz struktury na tekst string.
Przykład działania powyższego kodu:
Podaj współrzędną x: 25 Podaj współrzędną y: 75 Punkt P.x = 25; P.y = 75; znajduje się wewnątrz prostokąta Prostokąt: lewy = 10; prawy = 100; górny = 10; dolny = 200 Punkt P.x = 25; P.y = 75; nie znajduje się wewnątrz prostokąta lewy = 0; prawy = 50; górny = 0; dolny = 50
Właściwości struktury
Struktury mogą być wyposażone w mechanizmy kontrolowanego dostępu do pól. Tymi mechanizmami są właściwości, które można utworzyć w sposób ręczny wpisując kod, lub (w Visual Studio EE) z wykorzystaniem automatycznego mechanizmu generowania właściwości. Ten drugi sposób można wykorzystać klikając prawym przyciskiem myszy na prywatnym lub chronionym polu struktury i wybierając z menu kontekstowego pozycję Refactor→Encapsulate field... tak jak pokazano to na poniższej ilustracji.

Po kliknięciu wspomnianej wcześniej pozycji oczom twym ukazać powinno się okno dialogowe o nazwie Encapsulate Field, w którym to można zmienić nazwę właściwości. Domyślnie nazwa jest taka sama co pole struktury bądź klasy, której dotyczy z tą małą zmianą, że proponowana nazwa zaczyna się z dużej litery. Z tego powodu warto nazwy pól struktury czy klasy zaczynać od małej litery (dla łatwiejszego odróżnienia właściwości od pól). Oczywiście nazwę można zmienić a następnie zatwierdzić klikając przycisk OK.

Po wciśnięciu przycisku OK oczom naszym pokazać powinno się ostatnie okno dialogowe Preview Reference Changes - Encapsulate Field, w którym niezwłocznie należy kliknąć przycisk Apply.

Przykład kodu właściwości wygenerowanego dla pola left dla struktury rectangle wygląda następująco:
- // Domyślnie prywatne pola klasy
- int left;
- // Właściwość dla pola left
- public int Left
- {
- get { return left; } // pobranie właściwości
- set { left = value; } // ustawianie właściwości
- }
W ten sam sposób należy wygenerować kod dla reszty pól klasy z lekką modyfikacją, która będzie wyglądała następująco:
- // Domyślnie prywatne pola klasy
- int left;
- // Właściwość dla pola left
- public int Left
- {
- get { return left; } // pobranie właściwości
- set
- { // ustawianie właściwości
- if (value > right)
- {
- int temp = right;
- right = value;
- left = temp;
- }
- else
- {
- left = value;
- }
- }
- }
- int right;
- // Właściwość dla pola right
- public int Right
- {
- get { return right; } // pobranie właściwości
- set
- { // ustawianie właściwości
- if (value < left)
- {
- int temp = left;
- left = value;
- right = temp;
- }
- else
- {
- right = value;
- }
- }
- }
- int top;
- // Właściwość dla pola top
- public int Top
- {
- get { return top; } // pobranie właściwości
- set
- { // ustawianie właściwości
- if (value > bottom)
- {
- int temp = bottom;
- bottom = value;
- top = temp;
- }
- else
- {
- top = value;
- }
- }
- }
- int bottom;
- // Właściwość dla pola bottom
- public int Bottom
- {
- get { return top; } // pobranie właściwości
- set
- { // ustawianie właściwości
- if (value < top)
- {
- int temp = top;
- top = value;
- bottom = temp;
- }
- else
- {
- bottom = value;
- }
- }
- }
Jak widać w powyższym kodzie wprowadziłem odrobinę kontroli nad tym, jak ustawiane są pola struktury rectangle. Przykład użycia właściwości struktury:
- var r = new rectangle(20, 50, 70, 100);
- Console.WriteLine(r.ToString());
- r.Left = 200; // tutaj wywołuję właściwość Left -> get
- Console.WriteLine("Lewy: " + r.Left); // tutaj wywołuję właściwość Right -> set
- Console.WriteLine("Prawy: " + r.Right); // tutaj wywołuję właściwość Right -> set
Wynik działania powyższego kodu:
Prostokąt: lewy = 20; prawy = 50; górny = 70; dolny = 100 Lewy: 50 Prawy: 200
Oczywiście możliwe jest utworzenie właściwości, która obsługuje jedynie zwracanie wartości get, lub ustawianie set.
Referencyjne przekazywanie argumentów metody
Typy proste oraz struktury przechowują wartości pól w związku z tym utworzę taką oto metodę wcześniej opisanej już struktury rectangle, która pokaże, co to tak naprawdę oznacza:
- public void GetLeftTop(int x, int y) // "pobieranie" współrzędnych lewego górnego narożnika
- {
- x = left;
- y = top;
- }
A teraz utwórzmy sobie taki oto kod:
- var r = new rectangle(10,20,30,50);
- int x = 0;
- int y = 0;
- r.GetLeftTop(x, y);
- Console.WriteLine("x: " + x + "; y: " + y);
Wynik działania:
x: 0; y: 0
Jak widzicie, zmienne x i y nie zostały zmodyfikowane, ponieważ nie są one zmiennymi typu referencyjnego. Co się stało tutaj? Otóż gdy przekazuję jakiś argument metodzie czy to struktury, czy to klasy to ten argument jest kopiowany. W przypadku typu wartościowego kopiowana jest wartość, w przypadku typu referencyjnego kopiowany jest adres pamięci. Na szczęście jest metoda obejścia tego mechanizmu z wykorzystaniem słowa kluczowego ref. Oto poprawiona deklaracja metody GetLeftTop:
- public void GetLeftTop(ref int x, ref int y) // pobieranie współrzędnych lewego górnego narożnika
- {
- x = left;
- y = top;
- }
Teraz kod wykorzystujący tę metodę będzie wyglądał następująco:
- var r = new rectangle(10,20,30,50);
- int x = 0;
- int y = 0;
- r.GetLeftTop(ref x, ref y);
- Console.WriteLine("x: " + x + "; y: " + y);
Wynik jego działania:
x: 10; y: 30

Tytuł:
Wzorce projektowe w .NET Core 3. Projektowanie zorientowane obiektowo z wykorzystaniem C# i F#
Autor:
Dmitri Nesteruk

Tytuł:
Jak pisać świetne gry 2D w Unity. Niezależne programowanie w języku C#
Autor:
Jared Halpern

Tytuł:
C# 9.0 w pigułce
Autor:
Joseph Albahari

Tytuł:
C# 9.0. Leksykon kieszonkowy
Autor:
Joseph Albahari, Ben Albahari

Tytuł:
C# 8.0. Kompletny przewodnik dla praktyków. Wydanie VII
Autor:
Mark Michaelis

Tytuł:
C# 8.0 w pigułce
Autor:
Joseph Albahari, Eric Johannsen

Tytuł:
Asynchroniczność i wielowątkowość w języku C#
Autor:
Grzegorz Lang

Tytuł:
C# 8.0. Programowanie. Tworzenie aplikacji Windows, internetowych oraz biurowych
Autor:
Ian Griffiths

Tytuł:
C# 8.0. Leksykon kieszonkowy
Autor:
Joseph Albahari, Ben Albahari

Tytuł:
Wzorce projektowe w .NET. Projektowanie zorientowane obiektowo z wykorzystaniem C# i F#
Autor:
Dmitri Nesteruk