Struktury

Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 2732 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
Propozycje książek
tytuł: C# 10. Programowanie. Tworzenie aplikacji Windows, internetowych i biurowych autor: Ian Griffiths

Tytuł:

C# 10. Programowanie. Tworzenie aplikacji Windows, internetowych i biurowych

Autor:

Ian Griffiths

tytuł: C# 10 i .NET 6 dla programistów aplikacji wieloplatformowych. Twórz aplikacje, witryny WWW oraz serwisy sieciowe za pomocą ASP.NET Core 6, Blazor i EF Core 6 w Visual Studio 2022 i Visual Studio Code. Wydanie VI autor: Mark J. Price

Tytuł:

C# 10 i .NET 6 dla programistów aplikacji wieloplatformowych. Twórz aplikacje, witryny WWW oraz serwisy sieciowe za pomocą ASP.NET Core 6, Blazor i EF Core 6 w Visual Studio 2022 i Visual Studio Code. Wydanie VI

Autor:

Mark J. Price

tytuł: Visual Studio 2022, C# i .NET. Programowanie kontrolek autor: Łukasz Sosna

Tytuł:

Visual Studio 2022, C# i .NET. Programowanie kontrolek

Autor:

Łukasz Sosna

tytuł: C# 10. Leksykon kieszonkowy autor: Joseph Albahari, Ben Albahari

Tytuł:

C# 10. Leksykon kieszonkowy

Autor:

Joseph Albahari, Ben Albahari

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

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ł:

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 w pigułce

Autor:

Joseph Albahari

tytuł: C# 9.0. Leksykon kieszonkowy autor: Joseph Albahari, Ben 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. Kompletny przewodnik dla praktyków. Wydanie VII

Autor:

Mark Michaelis

tytuł: C# 8.0 w pigułce autor: Joseph Albahari, Eric Johannsen

Tytuł:

C# 8.0 w pigułce

Autor:

Joseph Albahari, Eric Johannsen

W związku z tym, że firma Helion nie wywiązuje się z swoich zobowiązań naliczania prowizji za każdą zakupioną książkę a kontakt z ową frmą jest nie możliwy autor strony zmuszony został do zablokowania linkowania książek. Za wszelkie niedogodności z tym związane z góry przepraszam i obiecuję włączenie linkowania gdy tylko sprawa zostanie wyjaśniona