Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 2936 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:

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.

Menu kontekstowe - wybór pozycji Refactor->Encapsulate field...
Rys. 1
Menu kontekstowe - tworzenie właściwości za pomocą pozycji Refactor→Encapsulate field...

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.

Widok okna dialogowego Encapsulate Filed
Rys. 2
Widok okna dialogowego Encapsulate Filed

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.

Okno dialogowe Preview Reference Changes - Encapsulate Field
Rys. 3
Widok okna dialogowego Preview Reference Changes - Encapsulate Field

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