Obiekty w C# i VB.NET

Dwa główne języki platformy .NET są obiektowe. Mimo że różnią się składnią, pewne podstawowe pojęcia i koncepcje są identyczne - zarówno w C#, jak i VB.NET. A dzięki wspólnej platformie uruchomieniowej - system informatyczny bez trudności może łączyć moduły napisane w obu tych językach oraz w dowolnym innym języku zgodnym z CLS.

Dwa główne języki platformy .NET są obiektowe. Mimo że różnią się składnią, pewne podstawowe pojęcia i koncepcje są identyczne - zarówno w C#, jak i VB.NET. A dzięki wspólnej platformie uruchomieniowej - system informatyczny bez trudności może łączyć moduły napisane w obu tych językach oraz w dowolnym innym języku zgodnym z CLS.

Jak w każdym języku obiektowym - w C# i w VB.NET można deklarować klasy, dziedziczyć po nich, tworzyć instancje, przesłaniać składowe w strukturach potomnych itp.

Klasa może zawierać następujące elementy:

  • pola (zmienne) ustalonego typu,
  • procedury (elementy, które nie zwracają wartości),
  • funkcje (elementy zwracające wartość),
  • właściwości (elementy, które pozwalają kontrolować dostęp do wartości "zawartych" w klasie),
  • delegaty (wskaźniki do funkcji).
Procedury i funkcje to pojęcia z VB.NET. W C# funkcja, która zwraca typ void, nie zwraca w rzeczywistości niczego i pełni funkcję procedury.

W .NET są dwa typy do definicji klas. Klasa może być deklarowana jak zwykła struktura (z użyciem słowa kluczowego struct). Może też być "pełnoprawną" klasą (deklaracja z użyciem słowa kluczowego class). Różnica polega na tym, że instancja obiektu zadeklarowanego jako class jest zawsze tworzona na stercie, a w wypadku struct możliwe jest tworzenie obiektu na stosie. Różnica polega również na tym, że elementy umieszczane na stosie mogą być znacznie szybciej kasowane (i tworzone). Ale struktura, jak klasa, może mieć metody, konstruktor itp.

Na podstawie definicji klasy może powstać instancja obiektu, czyli jego wystąpienie.

W klasie można zdefiniować metodę o tej samej nazwie, co nazwa klasy - pełni ona funkcję konstruktora. Gdy tworzymy obiekt (używając operatora new), właściwie wywołujemy tę funkcję. W ten sposób można np. wymagać, aby klasa opisująca klienta zawsze podczas tworzenia miała zainicjowane ID:

1 class Customer {

2 ...

3 public Customer(string id, string name, string surName) {...}

4 public Customer(string id) {...}

5 }

Konstruktorów może być, oczywiście, kilka - z różnymi zestawami parametrów. Można np. przyjąć, że konstruktor z wiersza 3 tworzy nowego klienta, a z wiersza 4, sprawdza, czy w bazie danych znajduje się wpis o danym ID - i jeżeli tak, to odpowiednio inicjuje pola w klasie.

Klasy mogą być ze sobą powiązane - mogą być dziedziczone, a część funkcjonalności może być przesłonięta. Mogą też być używane jako wynik działania funkcji czy parametr. Jednak - w .NET można stosować tylko "pojedyncze" dziedziczenie - innymi słowy, klasa może mieć tylko jednego rodzica (inaczej jest w C+ + , gdzie tzw. dziedziczenie wielobazowe jest dozwolone, ale rzadko stosowane). W .NET wprowadzono bardzo wygodny mechanizm - deklarowania tzw. interfejsów. Jest to zestaw funkcji, które klasa musi mieć (mówi się wtedy, że implementuje dany interfejs). Interfejs jest idealnym narzędziem do implementacji określonych typów zachowań. Klasa może dziedziczyć po innej klasie i dodatkowo implementować dowolną liczbę interfejsów. Na przykład IComparable określa jak porównywane będą obiekty danego typu, a ISerializable - własne mechanizmy serializacji. Co więcej, łatwo sprawdzić, czy klasa implementuje dany interfejs, i np. wymagać, aby w określonych sytuacjach była używana tylko klasa, która ma pewien zestaw interfejsów - bo wtedy wiadomo, że może wykonać określone operacje.

W VB.NET kod definicji interfejsu może mieć następującą postać:

Interface I2

Function FI2() As Integer

End Interface

Oznacza on, że jeżeli klasa implementuje interfejs I2, to musi mieć funkcję FI2 zwracającą liczbę typu Integer. Interfejs może zawierać: właściwości, zdarzenia, metody. Nie może zawierać pól (składowych) klasy. W kodzie C#:

interface I2 {

int FI2();

int Prop { get; set; };

int Info { get; };

dodatkowo określono, że klasa musi mieć dwie właściwości (omówione w dalszej części artykułu): Prop do odczytu i zapisu oraz Info tylko do odczytu.

Składowe klasy mogą mieć różny zakres widoczności - np. do składowych prywatnych może odwołać się tylko inna metoda klasy, ale już nie kod, który utworzył instancję danej klasy. Pozwala to ukryć pewne szczegóły implementacyjne przed wywołującym - zapewnić hermetyzację. Załóżmy, że mamy następujące klasy w C#:

using System;

public class ParentInCS {

public void ParentSomething() {

Console.WriteLine("CSParent");

}

public virtual void PrintSomething() {

Console.WriteLine("CSParent");

}

public

class ChildInCS : ParentInCS, IDisposable {

public void Dispose() { }

public int MyOwnFunction() {

return 1;

}

public override void PrintSomething() {

Console.WriteLine("CSChild");

}

}

Aby utworzyć instancję takiej klasy, można napisać:

ParentInCS p = new ParentInCS();

Następnie można wywołać metodę:

p.PrintSomething();

Klasa ChildInCS dziedziczy po ParentInCS i przestania funkcję PrintSomething. Dodatkowo implementuje interfejs IDisposable (o którym kilka stów można znaleźć w części "Garbage collector". Proszę zauważyć, że w kodzie należy wskazać zarówno to, które funkcje można przesłaniać (dodając stówo kluczowe virtual), jak i to, która funkcja przestania (stówo override).

Jeżeli użyjemy następującego kodu:

ChildInCS c = new ChildInCS();

c.PrintSomething();

c.ParentSomething();

c.MyOwnFunction();

Poziom widoczności elementu (zmiennej/ procedury/ właściwości)

Poziom widoczności elementu (zmiennej/ procedury/ właściwości)

to zostanie wywołana przesłonięta funkcja PrintSomething. Dodatkowo możemy sprawdzić, czy obiekt c implementuje jakiś interfejs, a jeżeli tak, to zrzutować obiekt na interfejs i używając zmiennej, która jest typu IDisposable, wywołać funkcję. (Oczywiście można bezpośrednio wywołać Dispose, ale ten przykład pokazuje, jak postępować, gdy nie znamy dokładnego typu obiektu, a chcemy tylko odwołać się do pewnego interfejsu, który powinien być zaimplementowany):

if (c is IDisposable) {

IDisposable d= c;

d.Dispose();

}

W VB.NET kod jest bardzo podobny:

Public Class ParentInVB

Public Overridable Sub PrintSomething()

Console.WriteLine("VBParent")

End Sub

Public Sub ParentSomething()

Console.WriteLine("VBParent")

End Sub

End Class

Public Class ChildInVB

Inherits ParentInVB

Implements IDisposable

Public Overloads Sub Dispose() Implements

IDisposable.Dispose

'Nie mamy zasobów niezarzadzanych

End Sub

Public Function MyOwnFunction() As Integer

Return 1

End Function

Public Overrides Sub PrintSomething()

Console.WriteLine("VBChild")

End Sub

End Class


Zobacz również