[ Pobierz całość w formacie PDF ]
systemu typów z wykorzystaniem metody GetCustomAttributes. Poniższy fragment kodu
odczytuje atrybuty Foo i Foo.Bar:
Type fooType = typeof(Foo);
object[] myAttrOnType = fooType.GetCustomAttributes(
typeof(MyAttribute), false);
if (myAttrOnType.Length > 0)
{
// Robimy coś specjalnego& typ ma atrybut MyAttribute
}
MethodInfo fooBarMethod = fooType.GetMethod("Bar");
foreach (object attr in fooBarMethod.GetCustomAttributes(false))
{
if (attr.GetType() == typeof(MyAttribute))
{
// Ma atrybut MyAttribute, zróbmy coś z tym
}
}
To był bardzo szybki przegląd atrybutów niestandardowych. W rozdziale 14. można znalezć
więcej informacji o pokazanych wyżej wywołaniach API, wewnętrznym działaniu atrybutów
niestandardowych (na przykład o formacie ich przechowywania) oraz ich roli w programo-
waniu dynamicznym.
Wyliczenia
Wyliczenie to specjalny typ, który odwzorowuje zbiór nazw na wartości liczbowe. Korzy-
stanie z wyliczeń jest alternatywą dla osadzania stałych w kodzie i zapewnia wyższy poziom
nominalnego bezpieczeństwa typologicznego. Typy wyliczeniowe wyglądają w metadanych
podobnie jak zwykłe typy, ale stosują się do ścisłych reguł zdefiniowanych w CTS. Na przy-
kład definiowanie metod lub konstruktorów w typach wyliczeniowych jest niedozwolone,
podobnie jak implementowanie interfejsów, i mogą one mieć tylko pojedyncze pole repre-
zentujące wartość. Reguły te istnieją po to, aby wyliczenia były wydajne i aby języki mogły
traktować je w pewien specyficzny sposób. Na szczęście większość języków (w tym C#)
oferuje składnię, która abstrahuje te reguły.
Typ wyliczeniowy wywodzi się z System.Enum, który z kolei wywodzi się z System.ValueTySe.
Każdy opiera się na konkretnym typie podstawowym, jednym spośród Boolean, Char, Byte,
Int16, Int32, Int64, SByte, UInt16, UInt32, UInt64, IntStr, UIntStr, Single i Double. W więk-
szości języków domyślnie używa się Int32; jest on dobrym kompromisem między zajmowaną
pamięcią a możliwością przyszłego rozszerzania wyliczenia o nowe wartości.
Instancja danego wyliczenia zawiera pojedyncze pole reprezentujące wartość. Wyliczenia
są typami wartościowymi, więc instancja wyliczenia jest zasadniczo równoważna wartości
typu podstawowego, z tym że odwołujemy się do niej według nazwy typu wyliczeniowego,
a przekształcenia w tę i z powrotem są dość łatwe.
Rozdział 2. Wspólny system typów 91
W C# w celu utworzenia nowego wyliczenia wystarczy napisać:
enum Color : byte
{
Czerwony,
Zielony,
Niebieski
}
Część określająca typ wyliczenia jako byte jest opcjonalna. Kompilator C# przekształca tę
definicję w metadane, które są zgodne z powyższymi regułami:
.class private auto ansi sealed Color
extends [mscorlib]System.Enum
{
.field public specialname rtspecialname uint8 value__
.field public static literal valuetype Color Czerwony = uint8(0x00)
.field public static literal valuetype Color Zielony = uint8(0x01)
.field public static literal valuetype Color Niebieski = uint8(0x02)
}
Wyliczenia Color można następnie użyć w programie. Wyobrazmy sobie, że pytamy użyt-
kownika o jego ulubiony kolor. Możemy użyć wyliczenia do zaprezentowania listy, prze-
analizowania danych wejściowych, a następnie do przekazywania wybranego koloru do różnych
procedur:
class ColorPick
{
static void Main()
{
Color favorite = SelectFavoriteColor();
RespondToFavoriteColor(favorite);
}
static Color SelectFavoriteColor()
{
Color favorite = (Color)(0xff);
// Program działa w pętli, dopóki nie zostanie wybrany prawidłowy kolor
do
{
// Wyświetlamy monit i listę prawidłowych kolorów
Console.Write("Wpisz swój ulubiony kolor (");
foreach (string name in Enum.GetNames(typeof(Color)))
Console.Write(" {0} ", name);
Console.Write("): ");
string input = Console.In.ReadLine();
try
{
favorite = (Color)Enum.Parse(typeof(Color), input, true);
}
catch (ArgumentException)
{
// Dane wpisane przez użytkownika nie pasują do nazw w wyliczeniu
Console.WriteLine("Błędny kolor, wybierz jeszcze raz!");
Console.WriteLine();
92 Część I Podstawowe informacje o CLR
}
}
while (favorite == (Color)(0xFF));
return favorite;
}
static void RespondToFavoriteColor(Color c)
{
// Zauważmy, że w C# można stosować instrukcję switch na wyliczeniu
switch (c)
{
case Color.Czerwony:
// Robimy coś dla osób, które lubią kolor czerwony
// &
break;
case Color.Zielony:
// Robimy coś dla osób, które lubią kolor zielony
// &
break;
case Color.Niebieski:
// Robimy coś dla osób, które lubią kolor niebieski
// &
break;
default:
// Wykryto nieznany kolor.
// Jest to prawdopodobnie błąd, ale musimy go obsłużyć!
break;
}
}
}
Zauważmy pomocnicze metody statyczne takie jak GetNames i Sarse należące do samego typu
Enum. Klasę Enum omówimy dokładniej w dalszej części rozdziału.
Wyliczenia znacznikowe
Najczęściej wartości wyliczenia wzajemnie się wykluczają, co oznacza, że dana instancja
wyliczenia może mieć tylko jedną z możliwych wartości. Czasem jednak pojedyncza instancja
musi reprezentować kombinację wartości. Typ wyliczeniowy może być oznaczony atrybu-
tem niestandardowym System.FlagsAttribute, który sprawia, że języki dopuszczają łączenie
i wyodrębnianie poszczególnych wartości z pojedynczej instancji. Rozważmy na przykład
zbiór uprawnień do operacji na pliku:
[Flags]
enum FileAccess
{
Read = 1,
Write = 2,
ReadWrite = 3;
}
Pliki często otwiera się jednocześnie do odczytu (Read) i zapisu (Write). Użycie wyliczenia
znacznikowego pozwala wyrazić tę ideę. Zauważmy, że liczbowe wartości Read i Write
są potęgami 2, począwszy od 1. Dlaczego? Otóż łączenie lub wyodrębnianie wartości
Rozdział 2. Wspólny system typów 93
z pojedynczej instancji odbywa się z wykorzystaniem bitowych operacji AND lub OR. Kolejne
elementy wyliczenia zajmowałyby wartości 4, 8, 16, 32 itd. Kompilator C# nie numeruje ich
automatycznie trzeba zrobić to ręcznie, bo w przeciwnym razie zostanie zastosowany
sekwencyjny sposób numerowania, który nie działałby prawidłowo z operacjami bitowymi.
Zauważmy, że w celu zasygnalizowania możliwości zapisu i odczytu dwie niezależne wartości
są logicznie sumowane: 1 | 2 to 3, stąd pomocnicza wartość ReadWrite. Dzięki niej w po-
niższym przykładzie druga instrukcja jest równoważna pierwszej, ale bardziej czytelna:
FileAccess rw1 = FileAccess.Read | FileAccess.Write; // wartość 3
FileAccess rw2 = FileAccess.ReadWrite; // wartość 3
Wyliczeń znacznikowych oczywiście nie można testować pod kątem równości, aby sprawdzić,
czy instancja (z kombinacją wartości) zawiera konkretną wartość. Jeśli na przykład spraw-
dzamy, czy instancja FileAccess zezwala na odczyt, moglibyśmy napisać:
FileAccess fa = /* & */;
if (fa == FileAccess.Read)
// mamy zezwolenie na odczyt
else
// nie mamy dostępu
Niestety, gdyby wyliczenie fa miało wartość FileAccess.ReadWrite, test zawiódłby i nie
uzyskalibyśmy dostępu do pliku. W większości przypadków byłoby to niewłaściwe. Musimy
[ Pobierz całość w formacie PDF ]