15-Bob: Enum Turlar va Bit Flaglar

Enumerated turlar (sanab o'tiladigan turlar), bit flaglar va enum turlarga kengaytirish metodlari qo'shish

Ushbu bobda men enum turlar (enumerated types) va bit flaglarni muhokama qilaman. Windows va ko'plab dasturlash tillari bu konstruksiyalarni uzoq yillar davomida ishlatib kelganligi sababli, ko'pchiligingiz enum turlar va bit flaglardan qanday foydalanishni allaqachon bilasiz deb o'ylayman. Biroq, common language runtime (CLR) va Framework Class Library (FCL) birgalikda ishlaydi va enum turlar hamda bit flaglarni haqiqiy obyektga yo'naltirilgan turlarga aylantiradi. Ular shunday ajoyib xususiyatlarni taklif qiladiki, men ko'pchilik dasturchilar bu xususiyatlar bilan tanish emas deb taxmin qilaman. Ushbu bobning asosiy mavzusi bo'lgan bu xususiyatlar dastur kodini yozishni qanchalik osonlashtirishi menga hayratlanarli.

Enum Turlar (Enumerated Types)

Enum tur — bu ramziy nom va qiymat juftliklarini belgilaydigan turdir. Masalan, quyida ko'rsatilgan Color turi ramzlar to'plamini belgilaydi, har bir ramz alohida rangni aniqlaydi.

internal enum Color {
    White,      // 0 qiymati tayinlanadi
    Red,        // 1 qiymati tayinlanadi
    Green,      // 2 qiymati tayinlanadi
    Blue,       // 3 qiymati tayinlanadi
    Orange      // 4 qiymati tayinlanadi
}

Albatta, dasturchilar doimo oq rangni ifodalash uchun 0, qizilni ifodalash uchun 1 va hokazolardan foydalanib dastur yozishlari mumkin. Biroq, dasturchilar raqamlarni kodga qattiq yozmasliklari kerak va buning o'rniga enum turdan foydalanishlari kerak, kamida ikkita sababga ko'ra:

  • O'qilishi va qo'llab-quvvatlanishi osonroq: Enum turlar bilan dasturni yozish, o'qish va qo'llab-quvvatlash ancha osonlashadi. Kodda har doim ramziy nom ishlatiladi va dasturchi har bir qattiq yozilgan qiymatning ma'nosini aqliy ravishda eslab yurishi shart emas (masalan, oq rang 0 mi yoki aksinchami). Bundan tashqari, ramzning raqamli qiymati o'zgarsa, kodga hech qanday o'zgartirish kiritmasdan qayta kompilyatsiya qilish kifoya. Shu bilan birga, hujjatlashtirish vositalari va debugger kabi yordamchi dasturlar dasturchi uchun mazmunli ramziy nomlarni ko'rsatishi mumkin.
  • Qattiq turlangan (strongly typed): Enum turlar qattiq turlangan. Masalan, kompilyator Color.Orange qiymatini Fruit enum turini kutayotgan metod parametri sifatida uzatishga uringaningizda xatolik beradi.

Microsoft .NET Framework da enum turlar kompilyator e'tibor beradigan oddiy ramzlardan ko'ra ko'proq narsadir. Enum turlar tur tizimida birinchi darajali fuqarolar (first-class citizens) sifatida ko'rib chiqiladi, bu esa boshqa muhitlarda (masalan, boshqarilmagan C++ da) enum turlar bilan amalga oshirib bo'lmaydigan juda kuchli amallarni bajarish imkonini beradi.

Har bir enum tur bevosita System.Enum dan hosila bo'ladi, System.Enum esa System.ValueType dan hosila bo'ladi, System.ValueType esa o'z navbatida System.Object dan hosila bo'ladi. Demak, enum turlar qiymat turlari (value types) bo'lib, ular unboxed va boxed shakllarda ifodalanishi mumkin. Biroq, boshqa value turlardan farqli o'laroq, enum tur hech qanday metod, xususiyat (property) yoki hodisa (event) aniqlay olmaydi. Lekin, enum turiga metod qo'shishni simulyatsiya qilish uchun C# ning extension methods xususiyatidan foydalanishingiz mumkin. Ushbu bobning oxiridagi "Enum Turlarga Metod Qo'shish" bo'limiga qarang.

Enum Turining Ichki Tuzilishi

Enum tur kompilyatsiya qilinganda, C# kompilyatori har bir ramzni turning konstanta maydoni (constant field) ga aylantiradi. Masalan, kompilyator yuqorida ko'rsatilgan Color enumeratsiyasini xuddi siz quyidagi kodni yozganingizdek ko'rib chiqadi:

internal struct Color : System.Enum {
    // Quyidagilar Color ning ramzlari va qiymatlarini belgilaydigan public konstantalar
    public const Color White  = (Color) 0;
    public const Color Red    = (Color) 1;
    public const Color Green  = (Color) 2;
    public const Color Blue   = (Color) 3;
    public const Color Orange = (Color) 4;

    // Quyidagi Color o'zgaruvchisining qiymatini saqlash uchun
    // public instansiya maydoni. Siz bu instansiya maydoniga
    // to'g'ridan-to'g'ri murojaat qiladigan kod yoza olmaysiz
    public Int32 value__;
}

C# kompilyatori bu kodni aslida kompilyatsiya qilmaydi, chunki u System.Enum dan hosila bo'ladigan turni aniqlashni taqiqlaydi. Biroq, bu psevdo-tur ta'rifi ichki tarzda nima sodir bo'layotganini ko'rsatadi. Asosan, enum tur shunchaki bir nechta konstanta maydonlar va bitta instansiya maydoni aniqlangan struktura (structure)dir. Konstanta maydonlar assemblining metama'lumotlariga chiqariladi va reflection orqali kiritilishi mumkin. Bu shuni anglatadiki, enum tur bilan bog'liq barcha ramzlar va ularning qiymatlarini ish vaqtida olishingiz mumkin. Shuningdek, satr (string) ramzini uning raqamli ekvivalentiga aylantirishingiz mumkin. Bu amallar System.Enum asosiy tur tomonidan taqdim etiladi, u bir nechta statik va instansiya metodlarini taklif qiladi. Men ularning bir qismini keyingi bo'limda muhokama qilaman.

Muhim

Enum tur tomonidan aniqlangan ramzlar konstanta qiymatlardir. Shuning uchun kompilyator enum tur ramziga havolani ko'rganida, ramzning raqamli qiymatini kompilyatsiya vaqtida almashtiradi va bu kod endi enum turni aniqlagan assembliga havola qilmaydi. Bu shuni anglatadiki, enum turni aniqlagan assembly ish vaqtida talab qilinmasligi mumkin — u faqat kompilyatsiya vaqtida kerak bo'lgan. Agar sizda enum turga havolalar emas, balki tur tomonidan aniqlangan ramzlarga havolalar bo'lgan kod bo'lsa, enum turini aniqlagan assembly ish vaqtida kerak bo'ladi. Ba'zi versiyalash muammolari paydo bo'lishi mumkin, chunki enum tur ramzlari faqat o'qiladigan qiymatlar (read-only values) emas, balki konstantalardir. Bu muammolarni men 7-bobning "Konstantalar" bo'limida tushuntirganman.

Enum Turining Asosiy Turi (Underlying Type)

Masalan, System.Enum turida GetUnderlyingType deb nomlangan statik metod mavjud va System.Type turida GetEnumUnderlyingType deb nomlangan instansiya metodi mavjud.

public static Type GetUnderlyingType(Type enumType);    // System.Enum da aniqlangan
public        Type GetEnumUnderlyingType();               // System.Type da aniqlangan

Bu metodlar enum tur qiymatini saqlash uchun ishlatiladigan asosiy (core) turni qaytaradi. Har bir enum turining asosiy turi mavjud bo'lib, u byte, sbyte, short, ushort, int (eng keng tarqalgan va C# standart tanlovi), uint, long yoki ulong bo'lishi mumkin. Albatta, bu C# primitiv turlari FCL turlariga mos keladi. Biroq, kompilyator implementatsiyasini soddalashtirish uchun C# kompilyatori bu yerda primitiv tur nomidan foydalanishni talab qiladi; FCL tur nomidan foydalanish (masalan, Int32) quyidagi xabar bilan xatolik hosil qiladi: error CS1008: Type byte, sbyte, short, ushort, int, uint, long, or ulong expected.

Quyidagi kod asosiy turi byte (System.Byte) bo'lgan enum turni qanday e'lon qilishni ko'rsatadi.

internal enum Color : byte {
    White,
    Red,
    Green,
    Blue,
    Orange
}

Color enum turi shu tarzda aniqlangan holda, quyidagi kod GetUnderlyingType nimani qaytarishini ko'rsatadi.

// Quyidagi satr "System.Byte" ni ko'rsatadi.
Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));

C# kompilyatori enum turlarni primitiv turlar sifatida ko'radi. Shu sababli, enum tur instansiyalarini boshqarish uchun ko'pgina tanish operatorlardan (==, !=, <, >, <=, >=, +, -, ^, &, |, ~, ++ va --) foydalanishingiz mumkin. Bu operatorlarning barchasi aslida har bir enum tur instansiyasi ichidagi value__ instansiya maydoni ustida ishlaydi. Bundan tashqari, C# kompilyatori enum tur instansiyalarini boshqa enum turiga yoki raqamli turga aniq ravishda cast qilish imkonini beradi. Shuningdek, enum tur instansiyasini raqamli turga aniq cast qilishingiz mumkin.

ToString va Format Metodlari

Enum tur instansiyasi berilganda, System.Enum dan meros olingan ToString metodini chaqirib, bu qiymatni bir nechta satr (string) ifodalashlaridan biriga moslashtirish mumkin.

Color c = Color.Blue;
Console.WriteLine(c);                    // "Blue"   (Umumiy format)
Console.WriteLine(c.ToString());         // "Blue"   (Umumiy format)
Console.WriteLine(c.ToString("G"));      // "Blue"   (Umumiy format)
Console.WriteLine(c.ToString("D"));      // "3"      (O'nlik format)
Console.WriteLine(c.ToString("X"));      // "03"     (O'n oltilik format)
Eslatma

O'n oltilik (hex) formatlashda ToString doimo katta harflarni chiqaradi. Bundan tashqari, chiqariladigan raqamlar soni enum ning asosiy turiga bog'liq: byte/sbyte uchun 2 ta raqam, short/ushort uchun 4 ta raqam, int/uint uchun 8 ta raqam va long/ulong uchun 16 ta raqam. Zarur bo'lsa, boshida nollar qo'yiladi.

ToString metodiga qo'shimcha ravishda, System.Enum turi enum turining qiymatini formatlash uchun chaqirishingiz mumkin bo'lgan statik Format metodini ham taklif qiladi.

public static String Format(Type enumType, Object value, String format);

Umuman olganda, men ToString metodini chaqirishni afzal ko'raman, chunki u kamroq kod yozishni talab qiladi va uni chaqirish osonroq. Lekin Format dan foydalanishning ToString ga nisbatan bitta afzalligi bor: Format qiymat parametri uchun raqamli qiymat uzatish imkonini beradi; enum tur instansiyasiga ega bo'lishingiz shart emas. Masalan, quyidagi kod "Blue" ni ko'rsatadi.

// Quyidagi satr "Blue" ni ko'rsatadi.
Console.WriteLine(Enum.Format(typeof(Color), (Byte)3, "G"));
Eslatma

Bir xil raqamli qiymatga ega bir nechta ramzga ega enum turini e'lon qilish mumkin. Umumiy formatlash yordamida raqamli qiymatni ramzga aylantirishda, Enum ning metodlari ramzlardan birining nomini qaytaradi. Biroq, qaysi ramz nomi qaytarilishiga kafolat yo'q. Bundan tashqari, agar siz qidirayotgan raqamli qiymat uchun hech qanday ramz aniqlanmagan bo'lsa, raqamli qiymatni o'z ichiga olgan satr qaytariladi.

GetValues va GetNames Metodlari

Shuningdek, System.Enum ning statik GetValues metodi yoki System.Type ning instansiya GetEnumValues metodini chaqirib, enum turidagi har bir ramziy nom uchun bitta element o'z ichiga olgan massivni olishingiz mumkin; har bir element ramziy nomning raqamli qiymatini o'z ichiga oladi.

public static Array GetValues(Type enumType);    // System.Enum da aniqlangan
public        Array GetEnumValues();               // System.Type da aniqlangan

Bu metodni ToString metodi bilan birga ishlatib, enum turining barcha ramziy va raqamli qiymatlarini ko'rsatishingiz mumkin:

Color[] colors = (Color[]) Enum.GetValues(typeof(Color));
Console.WriteLine("Number of symbols defined: " + colors.Length);
Console.WriteLine("Value\tSymbol\n-----\t------");
foreach (Color c in colors) {
    // Har bir ramzni O'nlik va Umumiy formatda ko'rsatish.
    Console.WriteLine("{0,5:D}\t{0:G}", c);
}

Oldingi kod quyidagi natijani beradi:

Number of symbols defined: 5
Value   Symbol
-----   ------
    0   White
    1   Red
    2   Green
    3   Blue
    4   Orange

Shaxsan men GetValues va GetEnumValues metodlarini yoqtirmayman, chunki ikkalasi ham Array qaytaradi va keyin tegishli massiv turiga cast qilish kerak bo'ladi. Shuning uchun men har doim o'zimning metodimni aniqlashni afzal ko'raman.

public static TEnum[] GetEnumValues<TEnum>() where TEnum : struct {
    return (TEnum[])Enum.GetValues(typeof(TEnum));
}

Mening generic GetEnumValues metodim bilan men kompilyatsiya vaqtida yaxshiroq tur-xavfsizlikni ta'minlay olaman va oldingi misoldagi birinchi satrni soddalashtiraman:

Color[] colors = GetEnumValues<Color>();

Bu muhokama enum turlar ustida bajarilishi mumkin bo'lgan ajoyib amallarning ba'zilarini ko'rsatadi. ToString metodi umumiy format bilan foydalanuvchi interfeysi elementlarida (ro'yxat qutilari, tanlash qutilari va shunga o'xshash) ramziy nomlarni ko'rsatish uchun juda ko'p ishlatiladi (agar satrlarni lokalizatsiya qilish kerak bo'lmasa, chunki enum turlar lokalizatsiyani qo'llab-quvvatlamaydi). GetValues metodiga qo'shimcha ravishda, System.Enum turi va System.Type turi enum turining ramzlarini qaytaruvchi quyidagi metodlarni ham taklif qiladi.

// Raqamli qiymat uchun satr ifodalashini qaytaradi
public static String GetName(Type enumType, Object value);  // System.Enum da aniqlangan
public        String GetEnumName(Object value);              // System.Type da aniqlangan

// Enum dagi har bir ramz uchun bittadan satrlar massivini qaytaradi
public static String[] GetNames(Type enumType);              // System.Enum da aniqlangan
public        String[] GetEnumNames();                       // System.Type da aniqlangan

Parse va TryParse Metodlari

Men enum tur ramzini qidirish uchun foydalanishingiz mumkin bo'lgan ko'plab metodlarni muhokama qildim. Lekin ramzning ekvivalent qiymatini qidirish imkonini beradigan metod ham kerak — bu foydalanuvchi matn maydoniga ramzni kiritganida foydali bo'lishi mumkin bo'lgan amal. Ramzni enum tur instansiyasiga aylantirish Enum ning statik Parse va TryParse metodlari yordamida oson amalga oshiriladi.

public static Object Parse(Type enumType, String value);
public static Object Parse(Type enumType, String value, Boolean ignoreCase);
public static Boolean TryParse<TEnum>(String value, out TEnum result) where TEnum: struct;
public static Boolean TryParse<TEnum>(String value, Boolean ignoreCase, out TEnum result)
    where TEnum : struct;

Quyida bu metoddan qanday foydalanishni ko'rsatuvchi kod berilgan.

// Orange 4 deb aniqlanganligi sababli, 'c' 4 ga initsializatsiya qilinadi.
Color c = (Color) Enum.Parse(typeof(Color), "orange", true);

// Brown aniqlanmaganligi sababli, ArgumentException tashlanadi.
c = (Color) Enum.Parse(typeof(Color), "Brown", false);

// Color enum ning 1 qiymatli instansiyasini yaratadi.
Enum.TryParse<Color>("1", false, out c);

// Color enum ning 23 qiymatli instansiyasini yaratadi.
Enum.TryParse<Color>("23", false, out c);

IsDefined Metodi

Va nihoyat, Enum ning statik IsDefined metodi va Type ning IsEnumDefined metodidan foydalanib:

public static Boolean IsDefined(Type enumType, Object value);  // System.Enum da aniqlangan
public        Boolean IsEnumDefined(Object value);              // System.Type da aniqlangan

raqamli qiymatning enum tur uchun qonuniy ekanligini aniqlashingiz mumkin.

// "True" ko'rsatadi, chunki Color Red ni 1 sifatida aniqlaydi
Console.WriteLine(Enum.IsDefined(typeof(Color), (Byte)1));

// "True" ko'rsatadi, chunki Color White ni 0 sifatida aniqlaydi
Console.WriteLine(Enum.IsDefined(typeof(Color), "White"));

// "False" ko'rsatadi, chunki katta-kichik harf sezgir tekshirish amalga oshiriladi
Console.WriteLine(Enum.IsDefined(typeof(Color), "white"));

// "False" ko'rsatadi, chunki Color 10 qiymatli ramzga ega emas
Console.WriteLine(Enum.IsDefined(typeof(Color), (Byte)10));

IsDefined metodi ko'pincha parametrni tekshirish (validation) uchun ishlatiladi. Mana bir misol.

public void SetColor(Color c) {
    if (!Enum.IsDefined(typeof(Color), c)) {
        throw(new ArgumentOutOfRangeException("c", c, "Invalid Color value."));
    }
    // Rangni White, Red, Green, Blue yoki Orange ga o'rnatish
    ...
}

Parametrni tekshirish muhim, chunki kimdir quyidagini chaqirishi mumkin:

SetColor((Color) 547);

547 ga mos keladigan hech qanday ramz yo'qligi sababli, SetColor metodi qaysi parametr noto'g'ri ekanligini va sababini ko'rsatuvchi ArgumentOutOfRangeException istisnosini tashlaydi.

Muhim

IsDefined metodi juda qulay, lekin undan ehtiyotkorlik bilan foydalanishingiz kerak. Birinchidan, IsDefined doimo katta-kichik harfga sezgir qidirish amalga oshiradi va uni katta-kichik harfni e'tiborsiz qoldiradigan qilib sozlab bo'lmaydi. Ikkinchidan, IsDefined ancha sekin, chunki u ichki tarzda reflection dan foydalanadi; agar siz har bir mumkin bo'lgan qiymatni qo'lda tekshirish uchun kod yozsangiz, ilovangiz ishlashi ancha yaxshiroq bo'ladi. Uchinchidan, siz IsDefined ni faqat enum turi IsDefined ni chaqirayotgan assembly bilan bir xil assembly da aniqlangan bo'lsagina ishlatishingiz kerak. Buning sababi: faraz qilaylik, Color enum bitta assembly da, SetColor metodi esa boshqa assembly da aniqlangan. SetColor metodi IsDefined ni chaqiradi va agar rang White, Blue yoki Orange bo'lsa, SetColor o'z ishini bajaradi. Biroq, kelajakda Color enum Purple ni qo'shish uchun o'zgartirilsa, SetColor endi Purple ga ham ruxsat beradi, lekin u hech qachon buni kutmagan va oldindan aytib bo'lmaydigan natijalar bilan ishlashi mumkin.

Va nihoyat, System.Enum turi Byte, SByte, Int16, UInt16, Int32, UInt32, Int64 yoki UInt64 instansiyasini enum turining instansiyasiga aylantiradigan statik ToObject metodlar to'plamini taklif qiladi.

Enum turlar doimo boshqa biror tur bilan birga ishlatiladi. Odatda, ular turning metod parametrlari yoki qaytarish turi, xususiyatlar (properties) va maydonlar (fields) uchun ishlatiladi. Ko'p uchraydigan savol — enum turni uni ishlatadigan tur ichida ichki (nested) tur sifatida aniqlash kerakmi yoki uni bitta darajada (same level) aniqlash kerakmi. FCL ni ko'rib chiqsangiz, enum tur odatda uni ishlatadigan klass bilan bir xil darajada aniqlanganini ko'rasiz. Buning sababi shunchaki dasturchi hayotini osonlashtirish — yoziladigan kod miqdorini kamaytirishdir. Siz ham enum turingizni nom konfliktlari haqida tashvishlanmaguningizcha bir xil darajada aniqlashingiz kerak.

Bit Flaglar

Dasturchilar ko'pincha bit flaglar to'plami bilan ishlashadi. System.IO.File turining GetAttributes metodini chaqirganingizda, u FileAttributes turining instansiyasini qaytaradi. FileAttributes turi Int32 ga asoslangan enum tur bo'lib, unda har bir bit faylning bitta atributini aks ettiradi. FileAttributes turi FCL da quyidagicha aniqlangan.

[Flags, Serializable]
public enum FileAttributes {
    ReadOnly          = 0x00001,
    Hidden            = 0x00002,
    System            = 0x00004,
    Directory         = 0x00010,
    Archive           = 0x00020,
    Device            = 0x00040,
    Normal            = 0x00080,
    Temporary         = 0x00100,
    SparseFile        = 0x00200,
    ReparsePoint      = 0x00400,
    Compressed        = 0x00800,
    Offline           = 0x01000,
    NotContentIndexed = 0x02000,
    Encrypted         = 0x04000,
    IntegrityStream   = 0x08000,
    NoScrubData       = 0x20000
}

Fayl yashirin ekanligini aniqlash uchun quyidagi kodni bajarasiz.

String file = Assembly.GetEntryAssembly().Location;
FileAttributes attributes = File.GetAttributes(file);
Console.WriteLine("Is {0} hidden? {1}", file, (attributes & FileAttributes.Hidden) != 0);
Eslatma

Enum klasi quyidagicha aniqlangan HasFlag metodini taklif qiladi.

public Boolean HasFlag(Enum flag);

Bu metod yordamida Console.WriteLine chaqiruvini quyidagicha qayta yozishingiz mumkin.

Console.WriteLine("Is {0} hidden? {1}", file,
    attributes.HasFlag(FileAttributes.Hidden));

Biroq, men HasFlag metodidan foydalanishni quyidagi sababga ko'ra tavsiya qilmayman: u Enum turida parametr qabul qiladi, unga uzatadigan har qanday qiymat boxinglanishi kerak va bu xotira ajratilishini talab qiladi.

Mana faylning atributlarini faqat o'qish (read-only) va yashirin (hidden) ga o'zgartirishni ko'rsatuvchi kod.

File.SetAttributes(file, FileAttributes.ReadOnly | FileAttributes.Hidden);

FileAttributes turi ko'rsatganidek, birlashtirish mumkin bo'lgan bit flaglar to'plamini ifodalash uchun enum turlardan foydalanish odatiy holdir. Biroq, enum turlar va bit flaglar o'xshash bo'lsa-da, ular bir xil semantikaga ega emas. Masalan, enum turlar yagona raqamli qiymatlarni ifodalaydi, bit flaglar esa yoqilgan yoki o'chirilgan bitlar to'plamini ifodalaydi.

[Flags] Atributi va Bit Flag Enum Aniqlash

Bit flaglarni aniqlash uchun ishlatiladigan enum turni aniqlashda, albatta, har bir ramzga aniq raqamli qiymat berish kerak. Odatda, har bir ramz alohida bitga ega bo'ladi. None deb nomlangan va 0 qiymatiga ega ramzni ham aniqlash odatiy holdir. Shuningdek, keng ishlatiladigan kombinatsiyalarni ifodalovchi ramzlarni ham aniqlashingiz mumkin (quyidagi ReadWrite ramziga qarang). Bundan tashqari, System.FlagsAttribute maxsus atributini enum turiga qo'llash juda tavsiya etiladi.

[Flags]    // C# kompilyatori "Flags" yoki "FlagsAttribute" ga ruxsat beradi.
internal enum Actions {
    None      = 0,
    Read      = 0x0001,
    Write     = 0x0002,
    ReadWrite = Actions.Read | Actions.Write,
    Delete    = 0x0004,
    Query     = 0x0008,
    Sync      = 0x0010
}

Actions enum tur bo'lganligi sababli, oldingi bo'limda tavsiflangan barcha metodlarni bit flag enum turlar bilan ishlashda ham ishlatishingiz mumkin. Biroq, ba'zi funksiyalar biroz boshqacha ishlasa yaxshi bo'lar edi. Masalan, quyidagi kodga qarang.

Actions actions = Actions.Read | Actions.Delete;   // 0x0005
Console.WriteLine(actions.ToString());               // "Read, Delete"

[Flags] Atributi bilan ToString Ishlashi

ToString chaqirilganda, u raqamli qiymatni uning ramziy ekvivalentiga tarjima qilishga harakat qiladi. Raqamli qiymat 0x0005 bo'lib, uning ramziy ekvivalenti yo'q. Biroq, ToString metodi Actions turida [Flags] atributi mavjudligini aniqlaydi va endi ToString raqamli qiymatni bitta qiymat emas, balki bit flaglar to'plami sifatida ko'radi. 0x0001 va 0x0004 bitlari yoqilganligi sababli, ToString quyidagi satrni hosil qiladi: "Read, Delete". Agar siz Actions turidan [Flags] atributini olib tashlasangiz, ToString "5" ni qaytaradi.

Men oldingi bo'limda ToString metodi uchuchta formatlash usulini taklif qilganini ko'rsatgan edim: "G" (umumiy), "D" (o'nlik) va "X" (o'n oltilik). Enum turining instansiyasini umumiy format yordamida formatlashda, tur avval [Flags] atributi mavjudligini tekshiradi. Agar bu atribut qo'llanilmagan bo'lsa, raqamli qiymatga mos keladigan ramz qidiriladi va qaytariladi. Agar [Flags] atributi qo'llanilgan bo'lsa, ToString quyidagicha ishlaydi:

  1. Enum tur tomonidan aniqlangan raqamli qiymatlar to'plami olinadi va raqamlar kamayish tartibida saralanadi.
  2. Har bir raqamli qiymat enum instansiyasidagi qiymat bilan bitwise-AND qilinadi va agar natija raqamli qiymatga teng bo'lsa, raqamli qiymat bilan bog'langan satr chiqish satriga qo'shiladi va bitlar hisobga olingan deb belgilanadi hamda o'chiriladi. Bu qadam barcha raqamli qiymatlar tekshirilgunicha yoki enum instansiyasining barcha bitlari o'chirilgunicha takrorlanadi.
  3. Agar barcha raqamli qiymatlar tekshirilgandan keyin enum instansiyasi hali 0 ga teng bo'lmasa, enum instansiyasida hech qanday aniqlangan ramzga mos kelmaydigan ba'zi bitlar yoqilgan. Bu holda, ToString enum instansiyasidagi asl raqamni satr sifatida qaytaradi.
  4. Agar enum instansiyasining asl qiymati 0 ga teng bo'lmasa, ramzlarning vergul bilan ajratilgan to'plami qaytariladi.
  5. Agar enum instansiyasining asl qiymati 0 bo'lsa va enum turida 0 ga mos keladigan ramz aniqlangan bo'lsa, shu ramz qaytariladi.
  6. Agar bu qadamga yetib kelsak, "0" qaytariladi.

Agar xohlasangiz, [Flags] atributisiz ham Actions turini aniqlashingiz va "F" formatdan foydalanib to'g'ri satrni olishingiz mumkin.

// [Flags]       // Hozircha kommentariya qilingan
internal enum Actions {
    None      = 0,
    Read      = 0x0001,
    Write     = 0x0002,
    ReadWrite = Actions.Read | Actions.Write,
    Delete    = 0x0004,
    Query     = 0x0008,
    Sync      = 0x0010
}

Actions actions = Actions.Read | Actions.Delete;   // 0x0005
Console.WriteLine(actions.ToString("F"));           // "Read, Delete"

Agar raqamli qiymatni ramzga moslashtirish mumkin bo'lmasa, qaytariladigan satr asl raqamli qiymatni ko'rsatuvchi o'nlik raqamni o'z ichiga oladi; hech qanday ramz satrda ko'rinmaydi.

E'tibor bering, enum turingizda aniqlanadigan ramzlar 2 ning darajalari bo'lishi shart emas. Masalan, Actions turi All deb nomlangan va 0x001F qiymatiga ega ramzni aniqlashi mumkin edi. Agar Actions turining instansiyasi 0x001F qiymatiga ega bo'lsa, instansiyani formatlash "All" ni o'z ichiga olgan satrni hosil qiladi. Boshqa ramz satrlari ko'rinmaydi.

Bit Flaglar bilan Parse va TryParse

Hozirgacha men raqamli qiymatlarni flaglar satriga qanday aylantirish haqida gapirdim. Shuningdek, vergul bilan ajratilgan ramzlar satrini raqamli qiymatga aylantirish mumkin, buning uchun Enum ning statik Parse va TryParse metodlarini chaqirish kerak. Quyidagi kod bu metoddan qanday foydalanishni ko'rsatadi.

// Query 8 deb aniqlanganligi sababli, 'a' 8 ga initsializatsiya qilinadi.
Actions a = (Actions) Enum.Parse(typeof(Actions), "Query", true);
Console.WriteLine(a.ToString());    // "Query"

// Query va Read aniqlanganligi sababli, 'a' 9 ga initsializatsiya qilinadi.
Enum.TryParse<Actions>("Query, Read", false, out a);
Console.WriteLine(a.ToString());    // "Read, Query"

// Actions enum ning 28 qiymatli instansiyasini yaratadi.
a = (Actions) Enum.Parse(typeof(Actions), "28", false);
Console.WriteLine(a.ToString());    // "Delete, Query, Sync"

Parse va TryParse chaqirilganda ichki tarzda quyidagi amallar bajariladi:

  1. Satrning boshi va oxiridagi barcha bo'sh joy belgilar (whitespace) olib tashlanadi.
  2. Agar satrning birinchi belgisi raqam, plyus belgisi (+) yoki minus belgisi (-) bo'lsa, satr raqam deb qabul qilinadi va raqamli qiymati satrning raqamli ekvivalentiga teng bo'lgan enum instansiyasi qaytariladi.
  3. Uzatilgan satr vergullar bilan ajratilgan tokenlar to'plamiga bo'linadi va har bir tokendan barcha bo'sh joylar olib tashlanadi.
  4. Har bir token satri enum turining aniqlangan ramzlari ichidan qidiriladi. Agar ramz topilmasa, Parse System.ArgumentException tashlaydi va TryParse false qaytaradi. Agar ramz topilsa, uning bitwise qiymati ishlab turgan natijaga bitwise-OR qilinadi va keyingi token qidiriladi.
  5. Agar barcha tokenlar qidirilgan va topilgan bo'lsa, ishlab turgan natija qaytariladi.

IsDefined Metodini Bit Flaglar bilan Ishlatmaslik

IsDefined metodini bit flag enum turlar bilan hech qachon ishlatmasligingiz kerak. Bu ikkita sababga ko'ra ishlamaydi:

  • Agar IsDefined ga satr uzatsangiz, u satrni qidirish uchun alohida tokenlarga bo'lmaydi; u satrni xuddi bitta katta ramz bo'lganidek qidiradi. Vergullar bilan enum aniqlash mumkin emasligi sababli, ramz hech qachon topilmaydi.
  • Agar IsDefined ga raqamli qiymat uzatsangiz, u enum tur uzatilgan raqamga mos keladigan yagona ramzni aniqlayotganligini tekshiradi. Bit flaglar uchun bu ehtimol kam bo'lganligi sababli, IsDefined odatda false qaytaradi.

Enum Turlarga Metod Qo'shish

Ushbu bobning boshida men enum turining bir qismi sifatida metod aniqlash mumkin emasligini aytgan edim. Ko'p yillar davomida bu meni qayg'ulantirdi, chunki enum turimga ba'zi metodlarni taqdim etishni xohlagan ko'p holatlar bo'lgan. Baxtga, men C# ning extension method (kengaytirish metodi) xususiyatidan (8-bobda "Metodlar" da muhokama qilingan) foydalanib, enum turga metod qo'shishni simulyatsiya qila olaman.

Agar men FileAttributes enum turiga ba'zi metodlarni qo'shmoqchi bo'lsam, extension metodlari bilan statik klass quyidagicha aniqlayman.

internal static class FileAttributesExtensionMethods {
    public static Boolean IsSet(this FileAttributes flags, FileAttributes flagToTest) {
        if (flagToTest == 0)
            throw new ArgumentOutOfRangeException("flagToTest", "Value must not be 0");
        return (flags & flagToTest) == flagToTest;
    }

    public static Boolean IsClear(this FileAttributes flags, FileAttributes flagToTest) {
        if (flagToTest == 0)
            throw new ArgumentOutOfRangeException("flagToTest", "Value must not be 0");
        return !IsSet(flags, flagToTest);
    }

    public static Boolean AnyFlagsSet(this FileAttributes flags, FileAttributes testFlags) {
        return ((flags & testFlags) != 0);
    }

    public static FileAttributes Set(this FileAttributes flags, FileAttributes setFlags) {
        return flags | setFlags;
    }

    public static FileAttributes Clear(this FileAttributes flags,
        FileAttributes clearFlags) {
        return flags & ~clearFlags;
    }

    public static void ForEach(this FileAttributes flags,
        Action<FileAttributes> processFlag) {
        if (processFlag == null) throw new ArgumentNullException("processFlag");
        for (UInt32 bit = 1; bit != 0; bit <<= 1) {
            UInt32 temp = ((UInt32)flags) & bit;
            if (temp != 0) processFlag((FileAttributes)temp);
        }
    }
}

Extension Metodlardan Foydalanish

Va mana bu metodlarning ba'zilarini chaqirishni ko'rsatuvchi kod. Ko'rib turganingizdek, kod xuddi enum turidagi metodlarni chaqirayotgandek ko'rinadi.

FileAttributes fa = FileAttributes.System;
fa = fa.Set(FileAttributes.ReadOnly);
fa = fa.Clear(FileAttributes.System);
fa.ForEach(f => Console.WriteLine(f));
Xulosa

Ushbu bobda siz enum turlarning CLR da qanday ishlashini, ularning ichki tuzilishini, asosiy turini va turli xil foydali metodlarni o'rgandingiz. ToString, Parse, TryParse, GetValues, GetNames va IsDefined metodlari enum turlar bilan ishlashni ancha osonlashtiradi. Shuningdek, bit flaglar enum turlardan qanday farq qilishini va [Flags] atributi ToString metodi xatti-harakatiga qanday ta'sir qilishini ko'rdingiz. Nihoyat, C# ning extension method xususiyatidan foydalanib enum turlarga metod qo'shishni simulyatsiya qilish mumkinligini o'rgandingiz.