Zagadnienia egzaminu 70-483 opisane w tej notatce:

  • tworzenie typów wartości value types.
  • tworzenie typów referencyjnych reference types.
  • tworzenie typów generycznych generic types
  • konstruktory constructors
  • metody methods
  • klasy classes
  • metody rozszerzające extension methods
  • parametry nazwane i opcjonalne optional and named parameters
  • właściwości indeksowane indexed properties
  • metody przeciążone overloaded methods
  • metody przesłonięte overriden methods
  • enkapsulacja encapsulation
  • właściwości properties
  • metody dostępowe accessor methods

Typy wartości

Typy wartości w C# dzielą się na dwie główne kategorie nazywane strukturami i wyliczeniami (structs and enumerations). Struktury dzielą się na typy numeryczne (numeric typesintegral typesfloating-point typesdecimals), typy logiczne (bool) i struktury zdefiniowane przez użytkownika (user-defined structs). Enumeratory są zbiorami typów deklarowanymi za pomocą słowa kluczowego enum.

Predefiniowane typy wartości

Wszystkie typy wartości dziedziczą z System.ValueType. typy wartości posiadają aliasy służące do łatwiejszej deklaracji typów. Poniższa tabela zawiera typy danych wartości języka C#:

Typ (alias) Wartości Rozmiar Typ .NET
bool true, false 1 byte System.Boolean
Byte 0–255 1 byte System.Byte
char 0000–FFFF Unicode 16-bit System.Char
decimal ±1.0 × 10^−28 to ±7.9 × 10^28 28–29 significant digits
double ±5.0 × 10^−324 to ±1.7 × 10^308 15–16 digits System.Double
enum User-defined set of name constants    
float ±1.5 × 10^−45 to ±3.4 × 10^38 7 digits System.Single
int –2,147,483,648 to 2,147,483,647 Signed 32-bit System.Int32
long 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 Signed 64-bit System.Int64
sbyte –128 to 127 Signed 8-bit System.SByte
short –32,768 to 32,767 Signed 16-bit System.Int16
struct Includes the numeric types listed in this table as well as bool and user-defined structs    
uint 0 to 4,294,967,295 Unsigned 32-bit System.UInt32
ulong 0 to 18,446,744,073,709,551,615 Unsigned 64-bit System.UInt64
ushort 0 to 65,535 Unsigned 16-bit System.Uint16

Pracując z typami deklarujemy zmienne tak aby konkretnego typu. Po deklaracji zmiennej możemy przypisać jej wartość. Przypisanie może być również częścią deklaracji.

// declare an integer variable
int myInt;

// and assign a value to it
myInt = 3;
// use declaration and assignment in one statement
int mySecondInt = 50;

Słowo kluczowe int jest aliasem i oznacza, że deklarujemy zmienną typu System.Int32.

Typy wartości posiadają pewne ograniczenia:

  • Nie można utworzyć nowego typu dziedziczącego z typu wartości.
  • typy wartości nie mogą zawierać wartości NULL.

Przykładowy program: 
using System;

internalclassProgram
{
privatestaticvoidMain(string[] args)
{
// create a variable to hold a value type using the alias form
// but don't assign a variable
int myInt;
var myNewInt = newint();

// create a variable to hold a .NET value type
// this type is the .NET version of the alias form int
// note the use of the keyword new, we are creating an object from
// the System.Int32 class
var myInt32 = new System.Int32();

// you will need to comment out this first Console.WriteLine statement
// as Visual Studio will generate an error about using an unassigned
// variable. This is to prevent using a value that was stored in the
// memory location prior to the creation of this variable
Console.WriteLine(myInt);

// print out the default value assigned to an int variable
// that had no value assigned previously
Console.WriteLine(myNewInt);

// this statement will work fine and will print out the default value for
// this type, which in this case is 0
Console.WriteLine(myInt32);
}
}

.NET Framework dostarcza domyślnych wartości dla typów wartości. Dla wszystkich typów numerycznych jest to ekwiwalent zera dla danego typu (0). Dla typu bool domyślną wartością jest false, dla char jest ‘\0’, dla enum jest (E)0 a dlastruct jest null.

.NET Framework przechowuje typy wartości na stosie w pamięci komputera. rezultatem tego jest to, że gdy przypiszemy typ wartości do innego to jego wartość zostanie skopiowana.

Struktury

Struktury lub po prostu struct, są typami wartości których można użyć do przechowywania zbiorów wartości. Maja pewne cechy wspólne z klasami, lecz mają także pewne ograniczenia. 
Język C# umożliwia użycie wielu mechanizmów do przechowywania zbiorów danych jak struktury, klasy, tablice, kolekcje itd. Każda z nich ma swoje wymagania i ograniczenia które dyktują jak lub gdzie używać każdej z nich. 
Struktur używa się przeważnie dla definicji typów zawierających kilka związanych ze sobą elementów danych, te elementy danych są reprezentowane przez pola takiej struktury. Przykład deklaracji struktury opisującej studenta:

publicstruct Student
{
publicstring firstName;
publicstring lastname;
publicchar initial;
publicdouble score1;
publicdouble score2;
publicdouble score3;
publicdouble score4;
publicdouble score5;
publicdouble average;
}

Struktura zawiera zbiór właściwości reprezentowanych poprzez zmienne typu wartości. Sama struktura też jest typem wartości lecz typem złożonym. 
Przykład utworzenia instancji struktury:

// create a new instance of the Student struct in code
Student myStudent = new Student();
// create a new instance of the Student struct without the new keyword
Student myOtherStudent;

Prosty przykład deklaracji, przypisania wartości do zmiennych i użycia struktury:

// create a new instance of the Student struct
Student myStudent = new Student();
// assign some values to the properties of myStudent
myStudent.firstName = "Fred";
myStudent.lastName = "Jones";
myStudent.score1 = 89;
myStudent.score2 = 95;
Console.Write("Student " + myStudent.firstName + " " + myStudent.lastName);
Console.Write(" scored " + myStudent.score1 + " on his/her first test. ");
// illegal statement, cannot use the type directly
// Visual Studio will indicate that an object reference is required
Student.firstName = "Fail";

Struktury mogą zawierać funkcje, konstruktory, stałe, indeksatory, operatory, zdarzenia i typy zagnieżdżone oraz mogą implementować interfejsy. Użycie konstruktorów w strukturach różni się od konstruktorów w klasach. Cechy konstruktorów w strukturach:

  • Konstruktory są opcjonalne lecz jeżeli występują to muszą posiadać parametry, konstruktor domyślny jest zabroniony.
  • Pola nie mogą być inicjalizowane w ciele struktury.
  • Pola mogą być inicjalizowane przez konstruktor lub po deklaracji struktury.
  • Konstruktor musi inicjalizować wszystkie pola struktury.
  • Zmienne prywatne mogą być inicjalizowane tylko w konstruktorze.
  • Utworzenie nowej instancji struktury bez operatora new nie wywoła konstruktora jeżeli jest obecny.
  • Jeżeli struktura zawiera typ referencyjny (klasę) jako jedną ze swych zmiennych to trzeba bezpośrednio wywołać konstruktor typu referencyjnego.

Przykład struktury z konstruktorem i funkcją:

// create a Student struct that uses a constructor
publicstruct Student
{
publicstring firstName;
publicstring lastname;
privatestring courseName;
publicStudent(string first, string last,string course)
{
this.firstName = first;
this.lastName = last;
this.courseName = course;
}
publicvoidcalcAverage()
{
double avg = ((score1 + score2 + score3 + score4 + score5) / 5);
this.average = avg;
}
}

Ponieważ struktury są przechowywane jako wartość na stosie to utworzenie ich kolekcji może powodować dużą konsumpcję pamięci.

Przykład programu używającego struktury:

using System;

publicstruct Book
{
publicstring title;

publicstring category;

publicstring author;

publicint numPages;

publicint currentPage;

publicdouble ISBN;

publicstring coverStyle;

publicBook(string title, string category, string author, int numPages, int currentPage, double isbn, string cover)
{
this.title = title;
this.category = category;
this.author = author;
this.numPages = numPages;
this.currentPage = currentPage;
this.ISBN = isbn;
this.coverStyle = cover;
}

publicvoidnextPage()
{
if (this.currentPage != this.numPages)
{
this.currentPage++;
Console.WriteLine("Current page is now: " + this.currentPage);
}
else
{
Console.WriteLine("At end of book.");
}
}

publicvoidprevPage()
{
if (this.currentPage != 1)
{
this.currentPage--;
Console.WriteLine("Current page is now: " + this.currentPage);
}
else
{
Console.WriteLine("At the beginning of the book.");
}
}
}
internalclassProgram
{
privatestaticvoidMain(string[] args)
{
Book myBook = new Book(
"MCSD Certification Toolkit (Exam 70-483)",
"Certification",
"Covaci, Tiberiu",
648,
1,
81118612095,
"Soft Cover"
);
Console.WriteLine(myBook.title);
Console.WriteLine(myBook.category);
Console.WriteLine(myBook.author);
Console.WriteLine(myBook.numPages);
Console.WriteLine(myBook.currentPage);
Console.WriteLine(myBook.ISBN);
Console.WriteLine(myBook.coverStyle);
myBook.nextPage();
myBook.prevPage();
}
}

Enumeratory

Microsoft definiuje enumeratory jako odrębny typ który zawiera zbiór nazwanych zmiennych zwanych listą enumeratora. Nawet jeżeli nazwiesz pola listy enumeratora to kompilator nada im wartość całkowitą startując od zera i inkrementując o jeden każdego kolejnego członka listy, jest to zachowanie domyślne. Można nadpisać domyślne zachowanie poprzez przypisanie wartości do listy enumeratora. 
Przykład użycia enumeratora dla miesięcy w roku:

// enum call Months, using an overidden initializer
enumMonths{Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sept, Oct, Nov, Dec};

Domyślnym typem stalych enumeratora jest INT. Można to zmienić poprzez użycie dwukropka po nazwie enumeratora i zadeklarowanie typu np:

// using a non-default data type for an enum
enumMonths : byte {Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sept, Oct, Nov, Dec};

Lista typów jest ograniczona do:

  • byte
  • sbyte
  • short
  • ushort
  • int
  • uint
  • long
  • ulong

Każda z wartości listy enumeratora może być deklarowana np:

// enumeration to depict airspeeds for aircraft
enumAirSpeeds
{
Vx = 55,
Vy = 65,
Vs0 = 50,
Vs1 = 40,
Vne = 120
{

Enumeratory czynią kod czytelniejszym dla programisty i pozwalają na jego ponowne użycie. Enumeratory posiadają również metody które ułatwiają ich użycie. 
Przykład użycia enumeratora:

internalclassProgram
{
privateenum Months
{
Jan = 1,

Feb,

Mar,

Apr,

May,

Jun,

Jul,

Aug,

Sept,

Oct,

Nov,

Dec
}

privatestaticvoidMain(string[] args)
{
string name = Enum.GetName(typeof(Months), 8);
Console.WriteLine("The 8th month in the enum is " + name);
Console.WriteLine("The underlying values of the Months enum:");
foreach (int values in Enum.GetValues(typeof(Months)))
{
Console.WriteLine(values);
}
}
}

Typy referencyjne

Programowanie zorientowane obiektowo pozwala na modelowanie obiektów świata rzeczywistego za pomocą klas. Klasy w języku C# są typami referencyjnymi. nazwa ta pochodzi od tego, że zmienne tego typu przechowują tylko referencje do aktualnego obiektu a ie jego wartość. 
W kodzie .NET istnieją dwa logiczne rodzaje pamięci komputerowej które są używane. Są one zwane stosem (stack) i stertą (heap). Stos jest zakresem pamięci zarezerwowanej przez system operacyjny do wykonywania aplikacji. Jest to miejsce gdzie .NET przechowuje typy proste. Jest to relatywnie mała ilość pamięci używana do wykonywania kodu. Przeważnie typy proste są tworzone i niszczone raczej szybko, istnieją tylko na czas w jakim aplikacja ich potrzebuje i dlatego stos jest w pewnym stopniu czysty w czasie wykonywania kodu. Jest to również powód tego, że otrzymujemy wyjątek braku pamięci (out-of-memory exception) jeżeli używamy nieskończonej pętli która przechowuje wartości na stosie. 
Sterta jest znacznie większym zakresem pamięci którego .NET używa do przechowywania utworzonych obiektów klas. Obiekt klasy potrafi potrzebować dużej ilości pamięci w zależności od rozmiaru klasy. Klasa posiada pola opisane typami prostymi, opisującymi cechy obiektu który klasa opisuje. Posiadają również metody które są funkcjonalnością którą klasa wystawia. 
Referencja jest adresem w pamięci. W ten sposób kiedy kod musi skopiować lub przypisać obiekt do innej zmiennej to kompilator kopiuje jedynie adres w pamięci a nie cały obiekt. 
Składnia tworzenia klasy:

classMyClass
{
// fields
// properties
// methods
// events
// delegates
// nested classes
}

Kolejność deklaracji poszczególnych komponentów klasy nie jest wymagana jak również nie wszystkie komponenty muszą być zadeklarowane. 
Pola klasy (fields) są cechami charakterystycznymi obiektu który opisujemy np dla samochodu może to być kolor, marka itp.
Właściwości (properties) są powiązane bezpośrednio z polami. Służą do kontroli dostępu do pól klasy. 
Metody (methods) są używane do udostępnienia funkcjonalności klasy np dla samochodu to włączenie silnika, przyspieszenie itp. 
Zdarzenia (events) są również funkcjonalnością klasy ale używaną w inny sposób. Można o nich myśleć jako o rzeczach które wydarzą się na podstawie jakiegoś zewnętrznego wpływu np gdy jakiś sensor wykryje problem to wywołane zostanie zdarzenie i komputer w samochodzie nasłucha, że zostało wyzwolone zdarzenie. Jest to sposób informowanie obiektu przez inne obiekty, że coś się wydarzyło. Obiekt który zgłasza zdarzenie jest nazywany event publisher a obiekt który je odbieraevent subscriber
Delegaty (delegates) są definiowane przez Microsoft jako typy które odwołują się do metody. W języku C# delegata może być połączona z metodą która posiada podobną sygnaturę (typy argumentów). 
Klasy zagnieżdżone (nested classes) są klasami które są zadeklarowane w ciele innej klasy.

Modyfikatory dostępu

  1. Public - Modyfikator dostępu, który deklaruje dostępność typu do którego jest przypisany. Jest to modyfikator z pełnym dostępem. Dostęp z poza ciała klasy lub struktury jest w nim dozwolony. Typy referencyjne i typy wartości oraz metody mogą być publiczne.
  2. Private - Modyfikator dostępu, który deklaruje dostępność typu do którego jest przypisany. Jest to modyfikator z najmniejszym dostępem. Dostęp możliwy jest tylko w ciele klasy lub struktury. Typy referencyjne i typy wartości oraz metody mogą być prywatne.
  3. Internal - Modyfikator dostępu, który deklaruje dostępność typu do którego jest przypisany. Dostęp tylko w plikach należących do tego samego .NET assembly.
  4. Protected - modyfikator dostępu członków obiektu. Dostęp możliwy jest tylko w tej samej klasie oraz klasach pochodnych.
  5. Abstract - modyfikator używany w klasach, mówi o tym, że ta klasa nie może posiadać instancji ale, że służy jako klasa bazowa dla innych klas w hierarchii dziedziczenia.
  6. Async - ustawia metodę lub wyrażenie lambda jako asynchroniczne. pozwala na uruchamianie długotrwałych procesów bez blokowania kodu wywołującego.
  7. Const - oznacza stałą, czyli pole które musi być zainicjowane w czasie deklaracji nie może być później zmieniane.
  8. Event - używany do deklaracji zdarzeń.
  9. Extern - używane by wskazać, że metoda została zadeklarowana i zaimplementowana na zewnątrz. Używane z zaimportowanymi plikami DLL.
  10. New - Kiedy używane z członkami klasy ten modyfikator chowa odziedziczonych członków z klasy bazowej. Używamy kiedy chcemy użyć własnej wersji komponentu klasy a nie wersji odziedziczonej.
  11. Override - Używany iedy chcemy zmienić funkcjonalność która została odziedziczona z klasy bazowej.
  12. Partial - klasy mogą być podzielone pomiędzy wieloma plikami w tym samym assembly. ten modyfikator mówi, że klasa jest podzielona pomiędzy plikami.
  13. Readonly - zmienne z tym modyfikatorem mogą być inicjowane jedynie podczas deklaracji lub w konstruktorze.
  14. Sealed - Używane w klasach, oznacza, że z klasy nie można dziedziczyć.
  15. Static - Kiedy jest używane z obiektami klasy oznacza, że obiekt należy do klasy a nie poszczególnych jej instancji. Istnieje wiele takich klas w .NET które posiadają takie obiekty np klasy Math lub String.
  16. Unsafe - kod w C# jest kodem zarządzanym co oznacza, że operacje na pamięci są obsługiwane w chroniony sposób. Używając tego modyfikatora deklarujemy, że kontekst nie jest bezpieczny w odniesieniu do zarządzania pamięcią. Wskaźniki języka C++ są przykładem kodu który nie jest bezpieczny i musi być deklarowany z tym modyfikatorem.
  17. Virtual - zezwala na przesłonięcie metod i właściwości w klasach dziedziczących.
  18. Volatile - Modyfikator pól który umożliwia modyfikowanie pola przez komponenty inne niż twój kod np przez system operacyjny.

Definiowanie pól

Pola mają dwa podstawowe typy, mogą być statyczne lub należeć do instancji klasy. Każda klasa może posiadać oba typy jednocześnie. Pola instancji to te które należą do każdej instancji klasy. Przykład deklaracji pól w klasie i utworzenia instancji:

internalclassStudent
{
publicstaticint StudentCount;

publicstring firstName;

publicstring lastName;

publicstring grade;
}

internalclassProgram
{
privatestaticvoidMain(string[] args)
{
Student firstStudent = new Student();
Student.StudentCount++;
Student secondStudent = new Student();
Student.StudentCount++;
firstStudent.firstName = "John";
firstStudent.lastName = "Smith";
firstStudent.grade = "six";
secondStudent.firstName = "Tom";
secondStudent.lastName = "Thumb";
secondStudent.grade = "two";
Console.WriteLine(firstStudent.firstName);
Console.WriteLine(secondStudent.firstName);
Console.WriteLine(Student.StudentCount);
}
}

Użycie konstruktorów

Konstruktor jest metodą która jest wywoływana kiedy tworzona jest instancja obiektu klasy. W konstruktorze można ustawić startowe wartości pól klasy. jeżeli nie zdefiniujemy żadnego konstruktora to kompilator C# utworzy konstruktor domyślny. konstruktor domyślny ustawia wartość każdego pola do jego wartości domyślnej. 
Składnia konstruktora:

// constructor syntax
publicClassName(/* list of params */)
{
//optional initializing statements;
}

Konstruktor dla klasy student:

// constructor for the Student class
classStudent
{
publicstaticint StudentCount;
publicstring firstName;
publicstring lastName;
publicstring grade;
publicStudent(string first, string last, string grade)
{
this.firstName = first;
this.lastName = last;
this.grade = grade;
}
publicStudent()
{
}
}

W powyższym kodzie utworzone zostały dwa konstruktory. Konstruktor używa modyfikatora public ponieważ musi być dostępny z poza klasy. Konstruktor ma zawsze taką samą nazwę jak klasa. pierwszy konstruktor w kodzie powyżej przyjmuje listę parametrów i inicjuje ich wartościami pola instancji klasy. Drugi konstruktor jest konstruktorem domyślnym który jest wygenerowany zawsze przez kompilator jeżeli nie zadeklarujemy innego. Kompilator decyduje który konstruktor użyć na podstawie listy parametrów wejściowych których użyjemy przy tworzeniu instancji klasy.

Metody

Metody są komponentami w aplikacji które umożliwiają podział wymagań aplikacji na mniejsze kawałki. Dobrą praktyką jest tworzenie metod które odpowiadają tylko za jeden oddzielny kawałek funkcjonalności i metoda wykonuje tylko to co jet wymagane do osiągnięcia oczekiwanego rezultatu. Pozwala to na utrzymanie czystszego i bardziej czytelnego kodu a co za tym idzie łatwiejszego lokalizowania błędów. 
Metoda jest konstrukcją w kodzie która posiada nazwę, sygnaturę, blok wyrażeń i opcjonalne wyrażenie zwracające wynikreturn, które jest używane jeżeli metoda ma zadeklarowany zwracany typ wyniku. Jeżeli metoda jest typu void to użycie wyrażenia return spowoduje błąd kompilacji. 
Składnia metody:

// method syntax
modifier returntype name(optional arguments)
{
statements;
}

Przykład użycia metod w klasie Student:

internalclassStudent
{
publicstaticint StudentCount;

publicstring firstName;

publicstring lastName;

publicstring grade;

publicstringConcatenateName()
{
string fullName = this.firstName + " " + this.lastName;
return fullName;
}

publicvoidDisplayName()
{
string name = this.ConcatenateName();
Console.WriteLine(name);
}
}

internalclassProgram
{
privatestaticvoidMain(string[] args)
{
Student firstStudent = new Student();
Student.StudentCount++;
Student secondStudent = new Student();
Student.StudentCount++;
firstStudent.firstName = "John";
firstStudent.lastName = "Smith";
firstStudent.grade = "six";
secondStudent.firstName = "Tom";
secondStudent.lastName = "Thumb";
secondStudent.grade = "two";
firstStudent.DisplayName();
}
}

Przykład metody przyjmującej parametry na wejściu:

// sample method signature to accept values
public int sum(int num1, int num2)
{
return num1 + num2;
}
int sumValue = sum(2, 3);

Metody przeciążone

Metody są definiowane poprzez modyfikator, zwracany typ, nazwę i liczbę oraz typ parametrów wejściowych. Sygnaturą metody jest to co ją unikanie identyfikuje z pośród innych metod o tej samej nazwie. Sygnatura metody to jej nazwa oraz ilość i typy parametrów. metody przeciążone to metody o tej samej nazwie lecz o innej sygnaturze. 
Przykład metod przeciążonych obliczających pole powierzchni dla koła i prostokąta:

// calculate the area of a circle
publicdoublecalcArea(double radius)
{
double area = Math.Pi * (r*r);
return area;
}
// calculate the area of a rectangle
publicdoublecalcArea(double length, double width)
{
double area = length * width;
return area;
}

Bardzo często używanymi metodami przeciążonymi są konstruktory klas. pozwalają one na selektywne inicjowanie członków klasy.

Metody abstrakcyjne i przesłonięte

Metody abstrakcyjne są metodami które nie opisują żadnego obiektu ale są utworzone z naciskiem na wewnętrzną formę i strukturę klasy. W programowaniu obiektowym są to metody dla których deklaruje się sygnaturę bez konkretnej implementacji. Zwane są one również metodami wirtualnymi (virtual method). Klasy które dziedziczą z tej klasy muszą same zaimplementować ciała tych metod. klasa dziedzicząca musi w ten sposób przesłonić klasę abstrakcyjną poprzez implementację np.

// an abstract method inside a class
publicabstractclassStudent
{
publicabstractvoidoutputDetails();
}

Metoda abstrakcyjna nie posiada implementacji w tym przykładzie i jest zakończona średnikiem. metody abstrakcyjne mogą być deklarowane tylko w klasach abstrakcyjnych. 
Przykład klasy abstrakcyjnej i klasy która ją przesłania:

// an abstract method inside a class
publicabstractclassStudent
{
publicabstractvoidoutputDetails();
}
publicclassCollegeStudent: Student
{
publicstring firstName;
publicstring lastName;
publicstring major;
publicdouble GPA;
publicoverridevoidoutputDetails()
{
Console.WriteLine("Student " + firstName + " " + lastName +
" enrolled in " + major + " is has a GPA of " + GPA);
}
}

Metody rozszerzające

Metody rozszerzające umożliwiają rozszerzenie istniejących klas lub typów poprzez dodanie nowych metod bez modyfikacji oryginalnych klas lub typów oraz bez ich rekompilacji. On wersji .NET 3.5 jest to jedyny sposób na dodanie funkcjonalności dla istniejących typów danych. 
Metody rozszerzające definiujemy publicznej statycznej klasie. Metoda rozszerzająca jest zawsze statyczna i przyjmuje jako parametr ze słowem kluczowym this które wskazuje na instancję klasy w której się pojawia. Przykład rozszerzenia typu INT:

public static class MyExtendedMethods
{
public staticint square(this int num)
{
intresult = 0;
result = num * num;
returnresult;
}
}

Jeżeli rozszerzymy typ object to metoda rozszerzająca będzie dostępna dla każdego typu.

Parametry opcjonalne i nazwane

Zwykle kiedy wywołujemy metodę która ma wiele parametrów to musimy wstawić argumenty w kolejności w jakiej metoda ma zadeklarowane te parametry. Parametry nazwane pozwalają na dokładne przypisanie wartości parametrowi wg jego nazwy, niezależnie od kolejności deklaracji parametrów. Używamy parametrów nazwanych poprzez poprzedzenie wartości argumentu nazwą parametru który ma ona inicjować i znakiem dwukropka np.

classProgram
{
staticvoidMain(string[] args)
{
double area = rectArea(length: 35.0, width: 25.5);
Console.WriteLine(area);
}
publicstaticdoublerectArea(double length, double width)
{
return length * width;
}
}

Parametry opcjonalne pozwalają na użycie tylko niektórych argumentów przy użyciu metody. Parametry opcjonalne mają wyznaczoną w deklaracji wartość domyślną. Parametry domyślne można uzywać w metodach, konstruktorach, indekserach i delegatach. Wartości domyślne mogą być stałymi lub typami wartości oraz być tworzone jako nowy typ za pomocą słowa kluczowego new. Parametry opcjonalne są definiowane na końcu listy parametrów np.

// sample methodwith optional parameters
public void displayName(string first, string initial = "", string last = "")
{
Console.WriteLine(first + " " + initial + " " + last);
}

Enkapsulacja

Enkapsulacja jest jednym z założeń programowania obiektowego. Polega na pakowaniu obiektów składowych danej klasy tak, aby były one dostępne tylko metodom wewnętrznym danej klasy . Enkapsulacja jest również używana do “chowania” szczegółów implementacji klasy i ochrony charakterystyk obiektu który klasa opisuje.

Właściwości (properties)

Właściwości są publicznymi metodami których używamy aby udostępnić prywatne zmienne klasy. Poprzez nadanie zminnym klasy modyfikatora prywatnego sprawiamy, że nie są one dostępne z poza ciała klasy. Używając właściwości definiujemy publiczny dostęp do zmiennych klasy które chcemy udostępnić na zewnątrz. 
Właściwości również używają modyfikatorów dostępu. Można użyć dla nich modyfikatorów publicprivateprotected,internal lub protected internal. Właściwości mogą być również statyczne, wirtualne i abstrakcyjne. 
pomimo tego, że właściwości wyglądają jak charakterystyki klasy to nimi nie są. Właściwości są jedynie dwoma kluczowymi metodami dostępu i modyfikacji zmiennej klasy. pierwsza z nich jest znana jako metoda get i jest używana do dostępu do zmiennej którą właściwość reprezentuje. Druga jest znana jako metoda set i służy do modyfikacji zmiennej którą właściwość reprezentuje. Składnia właściwości:

// sample propety syntax
classStudent
{
private firstName;
publicstring FirstName
{
get {return firstName;}
set { firstName = value;}
}
}

Tak jak w przykładzie w jeżyku C# rekomendowane jest aby prywatna zmienna deklarowana była z małej litery a właściwość posiadała taką samą nazwę rozpoczynającą się dużą literą lecz nie jest to obowiązek i nie powoduje żadnego błędu składni. 
Poprzez pominięcie którejś z metod właściwości możemy uczynić zmienną tylko do odczytu lub tylko do zapisu. 
Przykład klasy Student która używa enkapsulacji:

publicclassStudent
{
privatestring firstName;
privatechar middleInitial;
privatestring lastName;
privateint age;
privatestring program;
privatedouble gpa;
publicStudent(string first, string last)
{
this.firstName = first;
this.lastName = last;
}
publicstring FirstName
{
get { return firstName; }
set { firstName = value; }
}
publicstring LastName
{
get { return lastName; }
set { lastName = value; }
}
publicchar MiddleInitial
{
get { return middleInitial; }
set { middleInitial = value; }
}
publicint Age
{
get { return age; }
set
{
if (value > 6)
{
age = value;
}
else
{
Console.WriteLine("Student age must be greater than 6");
}
}
}

publicstring Program
{
get { return program; }
set { program = value; }
}
publicdouble GPA
{
get { return gpa; }
set
{
if (value <= 4.0)
{
gpa = value;
}
else
{
Console.WriteLine("GPA cannot be greater than 4.0");
}
}
}
publicvoiddisplayDetails()
{
Console.WriteLine(this.FirstName + " " + this.MiddleInitial + " " + this.LastName);
Console.WriteLine("Has a GPA of " + this.GPA);
}
}
classProgram
{
staticvoidMain(string[] args)
{
Student myStudent = new Student("Tom", "Thumb");
myStudent.MiddleInitial = 'R';
myStudent.Age = 15;
myStudent.GPA = 3.5;
myStudent.displayDetails();
}
}

Właściwości indeksowane

Właściwości indeksowane lub indeksery (indexers), zachowują się nieco inaczej od standardowych właściwości. podstawowym celem indeksowanych właściwości jest zezwolenie na dostęp do grup elementów jak do tablic. Właściwość indeksowana jest identyfikowana za pomocą słowa kluczowego this. W klasie lub strukturze może istnieć tylko jedna właściwość indeksowana. 
Przykład klasy reprezentującej 32 bitowy adres IP:

publicclassIPAddress
{
privateint[] ip;

publicintthis[int index]
{
get
{
returnthis.ip[index];
}
set
{
if (value == 0 || value == 1)
{
this.ip[index] = value;
}
else
{
thrownew Exception("Invalid value");
}
}
}
}

internalclassProgram
{
privatestaticvoidMain(string[] args)
{
IPAddress myIP = new IPAddress();

// initialize the IP address to all zeros
for (int i = 0; i < 32; i++)
{
myIP[i] = 0;
}
}
}

Typy generyczne

typy generyczne zostały dodane w wersji 2.0 języka C#. Atutem użycia typów generycznych jest to, że możesz projektować klasę i jej metody bez wyszczególniania typów jej elementów aż do czasu deklaracji i tworzenia instancji klasy. Zaletami typów generycznych są: ponowne użycie kodu, bezpieczeństwo typów i wydajność.

Definiowanie typów generycznych

Typy generyczne definiujemy za pomocą nawiasów ostrych (), gdzie T jest typem generycznym. Przykład definicji klasy generycznej kolejki:

// example of a generic style Queue
publicclassGenericQueue<T>
{
publicvoidEnqueue(T obj);
public T DeQueue();
}

typ generyczny posiada wszystkie cechy innych typów referencyjnych.

Używanie typów generycznych

Tworząc obiekt klasy GenericQueue należy wstawić typ obiektu zamiast znaku T. Przykład tworzenia instancji klasy:

// generic queue that will be used to store Student objects
GenericQueue<Student> StudentQueue = new GenericQueue<Student>();
Student myStudent = new Student("Tom", "Thumb");
// store the myStudent object in the StudentQueue
StudentQueue.Enqueue(myStudent);
// retrieve the myStudent object from the StudentQueue
StudentQueue.Dequeue();

Ponieważ typ referencyjny jest wyspecyfikowany w trakcie tworzenia instancji obiektu to nie trzeba rzutować typów.

Definiowanie metod generycznych

Metody generyczne są definiowane z parametrem typu w ostrym nawiasie tak jak klasy generyczne. Przykład metody generycznej używanej w algorytmach sortujących:

// example ofgenericmethodwithtype parameters
public voidSwap<T>(ref T valueOne, ref T valueTwo)
{
T temp = valueOne;
valueOne = valueTwo;
valueTwo = temp;
}

Metody generyczne używają słowa kluczowego ref przed użyciem typu generycznego w liście parametrów. Oznacza to, że argument będzie wstawiony poprzez referencję.

Użycie metod generycznych

Używając metod generycznych musimy wstawić poprawny typ w wywołaniu metody, zastępując parametr T. Przykład sortowania używającego zdefiniowanej metody generycznej Swap:

internal class Program
{
private staticvoidMain(string[] args)
{
int[] arrInts = new[] { 2, 5, 4, 7, 6, 7, 1, 3, 9, 8 };
char[] arrChar = new[] { 'f', 'a', 'r', 'c', 'h' };

// Sorting: integer Sort
for (int i = 0; i < arrInts.Length; i++)
{
for (int j = i + 1; j < arrInts.Length; j++)
{
if (arrInts[i] > arrInts[j])
{
swap<int>(ref arrInts[i], ref arrInts[j]);
}
}
}

// Sorting: character Sort
for (int i = 0; i < arrChar.Length; i++)
{
for (int j = i + 1; j < arrChar.Length; j++)
{
if (arrChar[i] > arrChar[j])
{
swap<char>(ref arrChar[i], ref arrChar[j]);
}
}
}
}

private staticvoid swap<T>(ref T valueOne, ref T valueTwo)
{
T temp = valueOne;
valueOne = valueTwo;
valueTwo = temp;
}
}

Użycie ograniczeń w typach generycznych

Typy generyczne mogą być ograniczane poprzez użycie klauzuli where. Poniższa lista zawiera listę różnych typów ograniczeń:
  • where T:struct - typ musi być typem wartości (tylko Nullable są niedozwolone).
  • where T : class - typ musi być typem referencyjnym (klasą, interfejsem, delegatą lub tablicą).
  • where T : new() - typ musi posiadać publiczny konstruktor domyślny.
  • where T : <base class name> - typ musi dziedziczyć z klasy bazowej lub być jej typu.
  • where T : <interface name> - typ musi implementować interfejs, możliwe jest użycie wielu interfejsów.
  • where T:U - typ T musi być typem U lub z niego dziedziczyć.
Przykład użycia klauzuli where w definicji klasy:
class MyClass<T> where T : class, new()
{
 public MyClass()
 {
 MyProperty = default(T);
 }
 T MyProperty { get; set; }
}
Słowo kluczowe default(T) inicjuje zmienną domyślną wartością typu.

Podsumowanie

  1. Typy wartości są najprostszymi typami używanymi prostych lub złożonych danych jak struktury i enumeratory.
  2. Struktura jest konstrukcją podobną do lekkiej klasy.
  3. Enumeracja czyni kod czytelniejszym poprzez nadanie nazw stałym wartościom.
  4. Enkapsulacja pozwala na chowanie szczegółów implementacyjnych klasy.
  5. Klasy i metody generyczne pozwalają na deklarację klas i metod bez wyszczególnionego typu.