[ Pobierz całość w formacie PDF ]

z której wywodzą się inne typy. Jeśli klasa nie jest abstrakcyjna, to nazywamy ją konkretną
(choć jest to cecha domyślna, więc zwykle się o niej nie wspomina). Poszczególne metody
klasy abstrakcyjnej mogą również być oznaczone jako abstrakcyjne, co znaczy, że nie do-
starczają implementacji. Właściwości nie mogą być oznaczone jako abstrakcyjne. Metody
abstrakcyjne są znane większości programistów C++ pod nazwą metod czysto wirtualnych.
Klasa abstrakcyjna nie musi zawierać metod abstrakcyjnych, ale typ z metodami abstrak-
cyjnymi musi być abstrakcyjny.
Na przykład każda z czterech poniższych klas może być oznaczona jako abstrakcyjna. Tylko
przykłady 2. i 4. muszą być oznaczone jako abstrakcyjne, ponieważ zawierają abstrakcyjne
składowe:
// Abstrakcyjna, nie zawiera składowych
abstract class AbstractType1 {}
// Abstrakcyjna, zawiera tylko abstrakcyjne składowe
abstract class AbstractType2
{
public abstract void Foo();
public abstract void Bar();
}
// Abstrakcyjna, zawiera tylko nieabstrakcyjne składowe
abstract class AbstractType3
{
public void Foo()
{
Console.WriteLine("AbstractType3::Foo");
}
public void Bar()
{
Console.WriteLine("AbstractType3::Bar");
}
}
// Abstrakcyjna, zawiera mieszankę składowych abstrakcyjnych i nieabstrakcyjnych
abstract class AbstractType4
{
public void Foo()
{
Console.WriteLine("AbstractType4::Foo");
}
public abstract void Bar();
}
Kiedy typ wywodzi się z klasy abstrakcyjnej, to dziedziczy wszystkie składowe typu bazo-
wego, tak jak w przypadku zwykłych klas. Jeśli jakaś metoda dostarcza implementacji, to
owa implementacja również jest dziedziczona. Jednak w przypadku metod oznaczonych jako
abstrakcyjne podklasa musi albo dostarczyć własnej implementacji każdej z nich, albo zade-
klarować, że sama jest klasą abstrakcyjną.

76 Część I Podstawowe informacje o CLR
Rozważmy na przykład klasę wywodzącą się z pokazanego wyżej typu AbstractTySe4:
class ConcreteType : AbstractType4
{
public override void Bar()
{
// Musimy tu dostarczyć implementacji, a w przeciwnym razie
// oznaczyć również klasę ConcreteType jako abstrakcyjną.
Console.WriteLine("ConcreteType::Bar");
}
}
Typy abstrakcyjne są oznaczone tokenem metadanych abstract w wygenerowanym kodzie
IL, a metody abstrakcyjne są oznaczone niejawnie jako abstract i virtual. Zatem klasa
pochodna działa tak, jakby przesłaniała każdą zwykłą metodę wirtualną w celu dostarczenia
implementacji. Podobnie jak w przypadku każdej innej metody wirtualnej, przesłonięcie nie
może zmieniać widoczności metody.
Interfejsy
Interfejs to specjalny typ, który nie zawiera implementacji metod, ale może być używany
przez inne typy w celu zadeklarowania obsługi jakiegoś zbioru publicznych wywołań API.
Na przykład poniższy interfejs definiuje metodę i trzy właściwości:
interface ICollection : IEnumerable
{
void CopyTo(Array array, int index);
int Count { get; }
bool IsSynchronized { get; }
object SyncRoot { get; }
}
Interfejsom zwykle nadaje się nazwy zaczynające się od wielkiej litery I; zwyczaj ten
wywodzi się z ery COM. Wszystkie interfejsy COM  również według konwencji  miały
nazwy zaczynające się na I.
Podobnie jak w przypadku abstrakcyjnych metod abstrakcyjnej klasy, nie określa się im-
plementacji składowych. W przeciwieństwie do klasy abstrakcyjnej interfejs nie może za-
wierać żadnej implementacji. Zauważmy też, że interfejs może wywodzić się z innego interfej-
su. W takim przypadku dziedziczy wszystkie składowe interfejsu bazowego, co oznacza, że
implementator musi podać konkretne wersje składowych zarówno interfejsu bazowego, jak
i pochodnego.
Kiedy typ implementuje interfejs, to musi zapewnić obsługę całego interfejsu. Można jednak
użyć klasy abstrakcyjnej i uniknąć implementowania niektórych składowych, oznaczając je
jako abstrakcyjne. Następnie można odwoływać się do tego typu i uzyskiwać do niego dostęp
za pośrednictwem zmiennych typizowanych jako dany interfejs. Jest to czyste dziedziczenie
interfejsu, ponieważ nie są w to zaangażowane żadne implementacje. Rozważmy poniższy
prosty interfejs i jego przykładową implementację:

Rozdział 2. Wspólny system typów 77
interface IComparable
{
int CompareTo(object obj);
}
struct Int32 : IComparable
{
private int value;
public int CompareTo(object obj)
{
if (!(obj is Int32))
throw new ArgumentException();
int num = ((Int32)obj).value;
if (this.value
return -1;
else if (this.value > num)
return 1;
return 0;
}
// &
}
Przy takiej definicji instancji Int32 można używać wszędzie tam, gdzie oczekiwany jest
interfejs IComSarable, na przykład jako argument metody, lokalną zmienną lub pole itd.
To samo dotyczy typów bazowych, z których wywodzi się interfejs. Na przykład interfejs
ICollection implementuje IEnumerable; zatem każdy typ, który implementuje ICollection,
może być traktowany jako ICollection albo IEnumerable. Ponieważ Int32 jest strukturą,
musi zostać opakowany, zanim będzie można go przekazać jako IComSarable.
Warto wspomnieć o kilku innych interesujących rzeczach:
To, że typ implementuje interfejs, jest częścią jego publicznego kontraktu. Oznacza to, że
implementowane metody interfejsu również muszą być publiczne. Jedynym wyjątkiem jest
prywatna implementacja interfejsu (opisywana poniżej), w której metoda jest nadal dostępna
przez wywołania interfejsu, ale w rzeczywistości pozostaje prywatna.
Programista może oznaczyć implementację metody jako wirtualną lub jako finalną (to dru-
gie ustawienie jest domyślne). Zauważmy, że ze względu na reimplementację interfejsów
(omówioną niżej) nie można zapobiec tworzeniu nowych slotów metod tego samego inter-
fejsu przez dalsze podklasy.
Ekspedycja metod interfejsu
Wywołanie interfejsu z grubsza przypomina wywołanie metody wirtualnej. Choć IL wygląda
tak samo  tzn. wywołanie interfejsu jest emitowane jako instrukcja callvirt  w rzeczywi-
stości ekspedycja metody wymaga dodatkowej warstwy pośredniości. Jeśli przyjrzymy się
kodowi maszynowemu tworzonemu przez JIT, zobaczymy przeszukiwanie mapy interfejsu
(dołączonej do tablicy metod) przeprowadzane w celu skorelowania implementacji interfejsu
z odpowiednim slotem w tablicy metod. W większości przypadków te dodatkowe koszty nie
stanowią problemu.

78 Część I Podstawowe informacje o CLR
Wspomniano już  a dotyczy to również niniejszej dyskusji  że wywoływanie wirtualnej
metody na typie wartościowym wymaga uprzedniego opakowania wartości. Wywołania ogra-
niczone (nowa funkcja w wersji 2.0) w niektórych sytuacjach pozwalają środowisku urucho-
mieniowemu zoptymalizować ten proces. Funkcja ta zostanie opisana dokładniej w rozdziale 3.
Dziedziczenie wielokrotne
CTS nie pozwala, aby typ dziedziczył po więcej niż jednej klasie bazowej. Jest to decyzja
podjęta na wczesnym etapie projektowania CLR i .NET Framework, odbiegająca od prak-
tyk stosowanych w niektórych językach programowania, szczególnie w C++. Dziedziczenie [ Pobierz całość w formacie PDF ]

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • moje-waterloo.xlx.pl
  •