Struktury

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

Listing 1
  1. rodzaj_dostępu struct nazwa_struktury : interfejs1, interfejs2, .... // dziedziczenie po interfejsach
  2. {
  3. rodzaj_dostępu typ_danych nazwa_pola1;
  4. rodzaj_dostępu typ_danych nazwa_pola2;
  5. rodzaj_dostępu nazwa_struktury(typ_danych _nazwa_pola1, typ_danych _nazwa_pola2) // konstruktor
  6. {
  7. nazwa_pola1 = _nazwa_pola1;
  8. nazwa_pola2 = _nazwa_pola2;
  9. }
  10. }

Utworzenie instancji struktury odbywa się w następujący sposób:

Listing 2
  1. 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:

Listing 3
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace Struktury
  6. {
  7. class Program
  8. {
  9. // ############################ STRUKTORA RECTANGLE ###############################
  10. public struct rectangle
  11. {
  12. // Domyślnie prywatne pola klasy
  13. int left;
  14. int right;
  15. int top;
  16. int bottom;
  17. // Publiczny konstruktora
  18. public rectangle(int left, int right, int top, int bottom)
  19. {
  20. this.left = left;
  21. this.right = right;
  22. this.top = top;
  23. this.bottom = bottom;
  24. }
  25. // Publiczna metoda struktury sprawdzająca, czy dany punkt znajduje się wewnątrz prostokąta
  26. public bool PtInRect(int x, int y)
  27. {
  28. return x >= left && x <= right && y >= top && y <= bottom;
  29. }
  30. // Publiczna metoda statyczna struktury, sprawdzająca czy dany punkt mieści się w podanym prostokącie
  31. public static bool PtInRect(int x, int y, int left, int right, int top, int bottom)
  32. {
  33. return x >= left && x <= right && y >= top && y <= bottom;
  34. }
  35. public override string ToString() // Przeładowanie standardowej metody ToString, która siedzi przyczajona niczym wojownik ninja w klasie bazowej Object
  36. {
  37. return "Prostokąt: lewy = " + left + "; prawy = " + right + "; górny = " + top + "; dolny = " + bottom;
  38. }
  39. }
  40. // ############################# MAIN #############################
  41. static void Main(string[] args)
  42. {
  43. rectangle r = new rectangle(10, 100, 10, 200);
  44. int x = 0;
  45. int y = 0;
  46. Console.Write("Podaj współrzędną x: ");
  47. try
  48. {
  49. x = int.Parse(Console.ReadLine());
  50. }
  51. catch { }
  52. Console.Write("Podaj współrzędną y: ");
  53. try
  54. {
  55. y = int.Parse(Console.ReadLine());
  56. }
  57. catch { }
  58. if (r.PtInRect(x, y)) // wywołanie metody wewnętrznej instancji struktury rectangle
  59. {
  60. Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; znajduje się wewnątrz prostokąta " + r.ToString());
  61. }
  62. else
  63. {
  64. Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; nie znajduje się wewnątrz prostokąta " + r.ToString());
  65. }
  66. int left = 0;
  67. int right = 50;
  68. int top = 0;
  69. int bottom = 50;
  70. if (rectangle.PtInRect(x, y, left, right, top, bottom)) // wywołanie metody statycznej struktury rectangle
  71. {
  72. Console.WriteLine("Punkt P.x = " + x + "; P.y = " + y + "; znajduje się wewnątrz prostokąta lewy = " + left + "; prawy = " + right + "; górny = " + top + "; dolny = " + bottom);
  73. }
  74. else
  75. {
  76. 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);
  77. }
  78. Console.ReadLine();
  79. }
  80. }
  81. }

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:

Listing 4
  1. // Domyślnie prywatne pola klasy
  2. int left;
  3. // Właściwość dla pola left
  4. public int Left
  5. {
  6. get { return left; } // pobranie właściwości
  7. set { left = value; } // ustawianie właściwości
  8. }

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:

Listing 5
  1. // Domyślnie prywatne pola klasy
  2. int left;
  3. // Właściwość dla pola left
  4. public int Left
  5. {
  6. get { return left; } // pobranie właściwości
  7. set
  8. { // ustawianie właściwości
  9. if (value > right)
  10. {
  11. int temp = right;
  12. right = value;
  13. left = temp;
  14. }
  15. else
  16. {
  17. left = value;
  18. }
  19. }
  20. }
  21. int right;
  22. // Właściwość dla pola right
  23. public int Right
  24. {
  25. get { return right; } // pobranie właściwości
  26. set
  27. { // ustawianie właściwości
  28. if (value < left)
  29. {
  30. int temp = left;
  31. left = value;
  32. right = temp;
  33. }
  34. else
  35. {
  36. right = value;
  37. }
  38. }
  39. }
  40. int top;
  41. // Właściwość dla pola top
  42. public int Top
  43. {
  44. get { return top; } // pobranie właściwości
  45. set
  46. { // ustawianie właściwości
  47. if (value > bottom)
  48. {
  49. int temp = bottom;
  50. bottom = value;
  51. top = temp;
  52. }
  53. else
  54. {
  55. top = value;
  56. }
  57. }
  58. }
  59. int bottom;
  60. // Właściwość dla pola bottom
  61. public int Bottom
  62. {
  63. get { return top; } // pobranie właściwości
  64. set
  65. { // ustawianie właściwości
  66. if (value < top)
  67. {
  68. int temp = top;
  69. top = value;
  70. bottom = temp;
  71. }
  72. else
  73. {
  74. bottom = value;
  75. }
  76. }
  77. }

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:

Listing 6
  1. var r = new rectangle(20, 50, 70, 100);
  2. Console.WriteLine(r.ToString());
  3. r.Left = 200; // tutaj wywołuję właściwość Left -> get
  4. Console.WriteLine("Lewy: " + r.Left); // tutaj wywołuję właściwość Right -> set
  5. 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:

Listing 7
  1. public void GetLeftTop(int x, int y) // "pobieranie" współrzędnych lewego górnego narożnika
  2. {
  3. x = left;
  4. y = top;
  5. }

A teraz utwórzmy sobie taki oto kod:

Listing 8
  1. var r = new rectangle(10,20,30,50);
  2. int x = 0;
  3. int y = 0;
  4. r.GetLeftTop(x, y);
  5. 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:

Listing 9
  1. public void GetLeftTop(ref int x, ref int y) // pobieranie współrzędnych lewego górnego narożnika
  2. {
  3. x = left;
  4. y = top;
  5. }

Teraz kod wykorzystujący tę metodę będzie wyglądał następująco:

Listing 10
  1. var r = new rectangle(10,20,30,50);
  2. int x = 0;
  3. int y = 0;
  4. r.GetLeftTop(ref x, ref y);
  5. Console.WriteLine("x: " + x + "; y: " + y);

Wynik jego działania:

x: 10; y: 30

Komentarze