13-Bob: Interfeyslar

Interfeys merosxo'rligi, yashirin va aniq amalga oshirish (EIMI), generik interfeyslar, interfeys cheklovlari, va bazaviy klass yoki interfeys tanlash bo'yicha ko'rsatmalar

Ko'plab dasturchilar ko'p merosxo'rlik (multiple inheritance) tushunchasi bilan tanish — bu bir nechta bazaviy klasslardan hosil bo'ladigan klassni aniqlash qobiliyati. Masalan, tasavvur qiling, TransmitData nomli klass bor, uning vazifasi ma'lumot uzatish, va ReceiveData nomli klass bor, uning vazifasi ma'lumot qabul qilish. Endi siz SocketPort nomli klass yaratmoqchisiz, uning vazifasi ma'lumot ham uzatish, ham qabul qilish. Buning uchun siz SocketPort ni TransmitData va ReceiveData dan hosil qilmoqchi bo'lardingiz.

Ba'zi dasturlash tillari ko'p merosxo'rlikni qo'llab-quvvatlaydi, bu SocketPort klassiga ikkita bazaviy klassdan hosil bo'lish imkonini beradi. Ammo, common language runtime (CLR) — va shuning uchun barcha boshqariladigan (managed) dasturlash tillari — ko'p merosxo'rlikni qo'llab-quvvatlamaydi. Ko'p merosxo'rlikni umuman taklif qilmaslik o'rniga, CLR interfeyslar (interfaces) orqali kichraytirilgan ko'p merosxo'rlikni taklif qiladi. Ushbu bobda interfeyslarni qanday aniqlash va ulardan foydalanish muhokama qilinadi, shuningdek interfeys yoki bazaviy klassdan qachon foydalanish kerakligini aniqlashga yordam beradigan ko'rsatmalar beriladi.

Klass va Interfeys Merosxo'rligi

Microsoft .NET Framework da System.Object degan klass bor, u to'rtta umumiy (public) instansiya metodlarini aniqlaydi: ToString, Equals, GetHashCode va GetType. Bu klass boshqa barcha klasslarning ildiz yoki yakuniy bazaviy klassidir — barcha klasslar Object ning to'rtta instansiya metodini meros oladi. Bu shuningdek, Object klassining instansiyasi ustida ishlaydigan kod haqiqatda har qanday klassning instansiyasi ustida amal bajarishi mumkinligini anglatadi.

Microsoft kompaniyasidagi kimdir Object ning metodlarini amalga oshirganligi sababli, Object dan hosil bo'lgan har qanday klass aslida quyidagilarni meros oladi:

  • Metod signaturalari — Bu kodga Object klassining instansiyasi bilan ishlayotganday o'ylash imkonini beradi, aslida esa biror boshqa klassning instansiyasi bilan ishlamoqda.
  • Ushbu metodlarning amalga oshirilishi — Bu Object dan hosil bo'lgan klassni aniqlaydigan dasturchi Object ning metodlarini qo'lda amalga oshirishdan ozod qiladi.

CLR da klass har doim bitta va faqat bitta klassdan hosil bo'ladi (u oxir-oqibat Object dan hosil bo'lishi kerak). Bu bazaviy klass ushbu metodlar uchun metod signaturalari va amalga oshirishlar to'plamini taqdim etadi. Yangi klass aniqlash haqida ajoyib narsa shuki, u kelajakda boshqa dasturchi tomonidan aniqlangan klass uchun bazaviy klass bo'lishi mumkin — barcha metod signaturalari va ularning amalga oshirishlari yangi hosil bo'lgan klass tomonidan meros olinadi.

CLR shuningdek dasturchilar interfeys aniqlash imkonini beradi, bu aslida metod signaturalari to'plamiga nom berishning bir usuli bo'lib, bu metodlar hech qanday amalga oshirish bilan kelmasligi mumkin. Klass interfeysni interfeys nomini ko'rsatish orqali meros oladi va klass CLR tur ta'rifini yaroqli deb hisoblashidan oldin interfeys metodlarining amalga oshirilishini aniq taqdim etishi kerak. Albatta, interfeys metodlarini amalga oshirish zerikarli bo'lishi mumkin, shuning uchun men interfeys merosxo'rligiga ko'p merosxo'rlikka erishish uchun kichraytirilgan mexanizm deb murojaat qilganman. C# kompilyatori va CLR aslida klassga bir nechta interfeyslarni meros olishga ruxsat beradi va, albatta, klass barcha meros olingan interfeys metodlari uchun amalga oshirishlar taqdim etishi kerak.

Klass merosxo'rligining ajoyib xususiyatlaridan biri shundaki, u hosil bo'lgan turning instansiyalarini bazaviy turning instansiyalari kutilgan barcha kontekstlarda almashtirishga ruxsat beradi. Xuddi shunday, interfeys merosxo'rligi interfeysni amalga oshiradigan turning instansiyalarini nomlangan interfeys turining instansiyalari kutilgan barcha kontekstlarda almashtirishga ruxsat beradi. Endi muhokamamizni aniqroq qilish uchun interfeyslarni qanday aniqlashni ko'rib chiqamiz.

Interfeysni Aniqlash

Oldingi bo'limda aytib o'tilganidek, interfeys nomlangan metod signaturalari to'plamidir. E'tibor bering, interfeyslar hodisalar (events), parametrsiz xususiyatlar (properties) va parametrli xususiyatlar (C# da indexerlar) ham aniqlashi mumkin, chunki bularning barchasi oldingi boblarda ko'rsatilganidek, metodlarga mos keladigan sintaksis qisqartmalaridir. Bundan tashqari, interfeys konstruktor metodlarni aniqlay olmaydi. Shuningdek, interfeys hech qanday instansiya maydonlarini aniqlashga ruxsat bermaydi.

Garchi CLR interfeysning statik metodlar, statik maydonlar, konstantalar va statik konstruktorlar aniqlashiga ruxsat bersa-da, Common Language Specification (CLS) ga mos interfeys bu statik a'zolarning hech biriga ega bo'lmasligi kerak, chunki ba'zi dasturlash tillari ularni aniqlash yoki ularga murojaat qilish imkoniyatiga ega emas. Aslida, C# interfeysda bu statik a'zolarning hech birini aniqlashga ruxsat bermaydi.

C# da siz interface kalit so'zidan foydalanib interfeys aniqlaysiz, unga nom va instansiya metod signaturalari to'plamini berasiz. Mana Framework Class Library (FCL) da aniqlangan bir nechta interfeysning ta'riflari:

public interface IDisposable {
    void Dispose();
}

public interface IEnumerable {
    IEnumerator GetEnumerator();
}

public interface IEnumerable<out T> : IEnumerable {
    new IEnumerator<T> GetEnumerator();
}

public interface ICollection<T> : IEnumerable<T>, IEnumerable {
    void    Add(T item);
    void    Clear();
    Boolean Contains(T item);
    void    CopyTo(T[] array, Int32 arrayIndex);
    Boolean Remove(T item);
    Int32   Count      { get; } // Faqat o'qish xususiyati
    Boolean IsReadOnly { get; } // Faqat o'qish xususiyati
}

CLR uchun interfeys ta'rifi xuddi tur ta'rifi kabi. Ya'ni, CLR interfeys tur obyekti uchun ichki ma'lumotlar strukturasini aniqlaydi va interfeys turining xususiyatlarini so'rash uchun refleksiyadan foydalanish mumkin. Turlar singari, interfeys fayl doirasida yoki boshqa tur ichida joylashtirilgan (nested) holda aniqlanishi mumkin. Interfeys turini aniqlaganingizda, xohlaganingizcha ko'rinuvchanlik/kirish darajasini (public, protected, internal va boshqalar) belgilashingiz mumkin.

Kelishuvga ko'ra, interfeys tur nomlari katta I harfi bilan boshlanadi, bu manba kodida interfeys turini oson ajratish imkonini beradi. CLR generik interfeyslarni (oldingi ba'zi misollardan ko'rishingiz mumkinidek) hamda interfeysdagi generik metodlarni qo'llab-quvvatlaydi. Generik interfeyslar tomonidan taklif qilinadigan ko'plab xususiyatlarni men ushbu bobda keyinroq va 12-bobda — "Generiklar" da batafsil muhokama qilaman.

Interfeys ta'rifi boshqa interfeyslarni "meros olishi" mumkin. Ammo, men bu yerda meros olish so'zini bir oz erkin ishlatyapman, chunki interfeys merosxo'rligi klass merosxo'rligi kabi aniq ishlamaydi. Men interfeys merosxo'rligini boshqa interfeyslarning shartnomalarini o'z ichiga olish deb o'ylashni afzal ko'raman. Masalan, ICollection<T> interfeys ta'rifi IEnumerable<T> va IEnumerable interfeyslarining shartnomalarini o'z ichiga oladi.

Bu shuni anglatadi:

  • ICollection<T> interfeysini meros olgan har qanday klass ICollection<T>, IEnumerable<T> va IEnumerable interfeyslarida aniqlangan barcha metodlarni amalga oshirishi kerak.
  • ICollection<T> interfeysini amalga oshiradigan turga ega obyektni kutgan har qanday kod shu obyektning turi IEnumerable<T> va IEnumerable interfeyslarining metodlarini ham amalga oshirishini taxmin qilishi mumkin.

Interfeysni Meros Olish

Ushbu bo'limda men interfeysni amalga oshiradigan turni qanday aniqlashni ko'rsataman, so'ngra shu turning instansiyasini yaratish va obyektda interfeys metodlarini chaqirish uchun qanday foydalanishni ko'rsataman. C# buni juda oddiy qiladi, lekin sahna ortida nima sodir bo'layotgani biroz murakkabroq. Sahna ortida nima sodir bo'layotganini ushbu bobda keyinroq tushuntiraman.

System.IComparable<T> interfeysi (MSCorLib.dll da) quyidagicha aniqlangan:

public interface IComparable<in T> {
    Int32 CompareTo(T other);
}

Quyidagi kod ushbu interfeysni amalga oshiradigan turni aniqlashni va ikkita Point obyektini solishtiradigan kodni ko'rsatadi:

using System;

// Point System.Object dan hosil bo'ladi va
// Point uchun IComparable<T> ni amalga oshiradi.
public sealed class Point : IComparable<Point> {
    private Int32 m_x, m_y;

    public Point(Int32 x, Int32 y) {
        m_x = x;
        m_y = y;
    }

    // Bu metod IComparable<T>.CompareTo() ni
    // Point uchun amalga oshiradi
    public Int32 CompareTo(Point other) {
        return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)
            - Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));
    }

    public override String ToString() {
        return String.Format("({0}, {1})", m_x, m_y);
    }
}
public static class Program {
    public static void Main() {
        Point[] points = new Point[] {
            new Point(3, 3),
            new Point(1, 2),
        };

        // Bu yerda Point ning IComparable<T> CompareTo metodi chaqiriladi
        if (points[0].CompareTo(points[1]) > 0) {
            Point tempPoint = points[0];
            points[0] = points[1];
            points[1] = tempPoint;
        }
        Console.WriteLine("Nuqtalar (0, 0) ga eng yaqindan eng uzoqqacha:");
        foreach (Point p in points)
            Console.WriteLine(p);
    }
}

C# kompilyatori interfeysni amalga oshiradigan metod public deb belgilanishini talab qiladi. CLR interfeys metodlarini virtual deb belgilanishini talab qiladi. Agar siz manba kodingizda metodni aniq virtual deb belgilamasangiz, kompilyator metodni virtual va sealed deb belgilaydi; bu hosil bo'lgan klassning interfeys metodini qayta yozishiga (override) to'sqinlik qiladi. Agar siz metodni aniq virtual deb belgilasangiz, kompilyator metodni virtual deb belgilaydi (va uni unsealed qoldiradi); bu hosil bo'lgan klassga interfeys metodini qayta yozishiga ruxsat beradi.

Agar interfeys metodi sealed bo'lsa, hosil bo'lgan klass metodni override qila olmaydi. Ammo, hosil bo'lgan klass xuddi shu interfeysni qayta meros olib, interfeys metodlari uchun o'z amalga oshirilishini taqdim etishi mumkin. Obyektda interfeys metodini chaqirganingizda, obyektning turiga bog'langan amalga oshirish chaqiriladi. Mana buni ko'rsatadigan misol:

using System;

public static class Program {
    public static void Main() {
        /************************** Birinchi Misol **************************/
        Base b = new Base();

        // Dispose ni b ning turi orqali chaqirish: "Base's Dispose"
        b.Dispose();

        // Dispose ni b ning obyekt turi orqali chaqirish: "Base's Dispose"
        ((IDisposable)b).Dispose();


        /************************** Ikkinchi Misol **************************/
        Derived d = new Derived();

        // Dispose ni d ning turi orqali chaqirish: "Derived's Dispose"
        d.Dispose();

        // Dispose ni d ning obyekt turi orqali chaqirish: "Derived's Dispose"
        ((IDisposable)d).Dispose();


        /************************** Uchinchi Misol *************************/
        b = new Derived();

        // Dispose ni b ning turi orqali chaqirish: "Base's Dispose"
        b.Dispose();

        // Dispose ni b ning obyekt turi orqali chaqirish: "Derived's Dispose"
        ((IDisposable)b).Dispose();
    }
}

// Bu klass Object dan hosil bo'ladi va IDisposable ni amalga oshiradi
internal class Base : IDisposable {
    // Bu metod yashirin ravishda sealed va override qilib bo'lmaydi
    public void Dispose() {
        Console.WriteLine("Base's Dispose");
    }
}

// Bu klass Base dan hosil bo'ladi va IDisposable ni qayta amalga oshiradi
internal class Derived : Base, IDisposable {
    // Bu metod Base ning Dispose ni override qila olmaydi. 'new' kalit so'zi
    // bu metod IDisposable ning Dispose metodini qayta amalga oshirishini bildiradi
    new public void Dispose() {
        Console.WriteLine("Derived's Dispose");

        // ESLATMA: Quyidagi satr bazaviy klassning amalga oshirilishini
        // chaqirishni ko'rsatadi (agar kerak bo'lsa)
        // base.Dispose();
    }
}

Interfeys Metodlarini Chaqirish Haqida Batafsil

FCL ning System.String turi System.Object ning metod signaturalari va ularning amalga oshirilishlarini meros oladi. Bundan tashqari, String turi bir nechta interfeyslarni ham amalga oshiradi: IComparable, ICloneable, IConvertible, IEnumerable, IComparable<String>, IEnumerable<Char> va IEquatable<String>. Bu shuni anglatadiki, String turi Object bazaviy turi taklif qiladigan metodlarni amalga oshirishi (yoki override qilishi) shart emas. Ammo, String turi barcha interfeyslarning barcha metodlarini amalga oshirishi kerak.

CLR sizga interfeys turida bo'lgan maydon, parametr yoki lokal o'zgaruvchilarni aniqlash imkonini beradi. Interfeys turining o'zgaruvchisidan foydalanish sizga shu interfeys tomonidan aniqlangan metodlarni chaqirish imkonini beradi. Bundan tashqari, CLR sizga Object tomonidan aniqlangan metodlarni ham chaqirishga ruxsat beradi, chunki barcha klasslar Object ning metodlarini meros oladi. Quyidagi kod buni ko'rsatadi:

// s o'zgaruvchisi String obyektiga ishora qiladi.
String s = "Jeffrey";
// s dan foydalanib, men String, Object, IComparable,
// ICloneable, IConvertible, IEnumerable va boshqa
// interfeyslarda aniqlangan istalgan metodni chaqira olaman.

// cloneable o'zgaruvchisi xuddi shu String obyektiga ishora qiladi
ICloneable cloneable = s;
// cloneable dan foydalanib, men ICloneable interfeysi tomonidan
// aniqlangan istalgan metodni (yoki Object tomonidan aniqlangan
// istalgan metodni) chaqira olaman.

// comparable o'zgaruvchisi xuddi shu String obyektiga ishora qiladi
IComparable comparable = s;
// comparable dan foydalanib, men IComparable interfeysi tomonidan
// aniqlangan istalgan metodni (yoki Object tomonidan aniqlangan
// istalgan metodni) chaqira olaman.

// enumerable o'zgaruvchisi xuddi shu String obyektiga ishora qiladi
// Ish vaqtida, siz o'zgaruvchini bir interfeysdan boshqasiga o'tkazishingiz
// mumkin, agar obyektning turi ikkala interfeysni ham amalga oshirsa.
IEnumerable enumerable = (IEnumerable) comparable;
// enumerable dan foydalanib, men IEnumerable interfeysi tomonidan
// aniqlangan istalgan metodni (yoki Object tomonidan aniqlangan
// istalgan metodni) chaqira olaman.

Bu kodda barcha o'zgaruvchilar managed heapdagi xuddi shu "Jeffrey" String obyektiga ishora qiladi, shuning uchun bu o'zgaruvchilarning birortasi yordamida chaqiradigan har qanday metod "Jeffrey" String obyektiga ta'sir qiladi. Ammo, o'zgaruvchining turi men obyekt ustida bajara oladigan amalni bildiradi. s o'zgaruvchisi String turida, shuning uchun men s dan foydalanib String turi tomonidan aniqlangan har qanday a'zolarni chaqira olaman (masalan, Length xususiyati). Men s o'zgaruvchisini Object dan meros olingan metodlarni chaqirish uchun ham ishlatishim mumkin (masalan, GetType).

cloneable o'zgaruvchisi ICloneable interfeys turiga ega, shuning uchun cloneable dan foydalanib, men bu interfeys tomonidan aniqlangan Clone metodini chaqira olaman. Bundan tashqari, men Object tomonidan aniqlangan metodlarni (masalan, GetType) chaqira olaman, chunki CLR barcha turlar Object dan hosil bo'lishini biladi. Ammo, cloneable dan foydalanib, men String tomonidan aniqlangan umumiy metodlarni yoki String amalga oshiradigan boshqa interfeys tomonidan aniqlangan metodlarni chaqira olmayman. Xuddi shunday, comparable o'zgaruvchisidan foydalanib, men CompareTo yoki Object tomonidan aniqlangan istalgan metodni chaqira olaman, lekin boshqa hech qanday metodlarni chaqira olmayman.

Muhim

Reference tur singari, value tur ham nol yoki undan ortiq interfeyslarni amalga oshirishi mumkin. Ammo, value tur instansiyasini interfeys turiga o'tkazganingizda (cast qilganingizda), value tur instansiyasi boxed bo'lishi kerak. Buning sababi shundaki, interfeys o'zgaruvchisi heapdagi obyektga ishora qilishi kerak bo'lgan havoladir, shunda CLR obyektning aniq turini aniqlash uchun obyektning tur obyekt ko'rsatkichini (type object pointer) tekshira olsin. Keyin, boxed value tur bilan interfeys metodini chaqirganingizda, CLR to'g'ri metodni topish uchun obyektning tur obyekt ko'rsatkichidan foydalanib tur metod jadvalini qidiradi.

Yashirin va Aniq Interfeys Metod Amalga Oshirishlari (Sahna Ortidagi Voqealar)

Tur CLR ga yuklanganda, tur uchun metod jadvali (method table) yaratiladi va initsializatsiya qilinadi (1-bobda "CLR ning Bajarish Modeli" da muhokama qilinganidek). Bu metod jadvali tur tomonidan kiritilgan har bir yangi metod, shuningdek tur tomonidan meros olingan har qanday virtual metodlar uchun yozuvni o'z ichiga oladi. Meros olingan virtual metodlar merosxo'rlik iyerarxiyasidagi bazaviy turlar tomonidan aniqlangan metodlarni, shuningdek interfeys turlari tomonidan aniqlangan metodlarni o'z ichiga oladi. Shunday qilib, agar sizda quyidagicha aniqlangan oddiy tur bo'lsa:

internal sealed class SimpleType : IDisposable {
    public void Dispose() { Console.WriteLine("Dispose"); }
}

turning metod jadvali quyidagilar uchun yozuvlarni o'z ichiga oladi:

  • Object — yashirin meros olingan bazaviy klass tomonidan aniqlangan barcha virtual instansiya metodlari.
  • IDisposable — meros olingan interfeys tomonidan aniqlangan barcha interfeys metodlari. Bu misolda faqat bitta metod bor, Dispose, chunki IDisposable interfeysi faqat bitta metodni aniqlaydi.
  • SimpleType tomonidan kiritilgan yangi metod, Dispose.

Dasturchi uchun ishni osonlashtirish maqsadida, C# kompilyatori SimpleType tomonidan kiritilgan Dispose metodi IDisposable ning Dispose metodi uchun amalga oshirish ekanligini taxmin qiladi. C# kompilyatori bu taxminni shuning uchun qiladiki, chunki metod public, va interfeys metodi bilan yangi kiritilgan metodning signaturalari bir xil. Ya'ni, metodlar bir xil parametr va qaytarish turlariga ega. Aytgancha, agar yangi Dispose metodi virtual deb belgilangan bo'lsa ham, C# kompilyatori uni interfeys metodi uchun mos deb hisoblagan bo'lardi.

C# kompilyatori yangi metodni interfeys metodiga moslaganida, u metama'lumotlarni chiqaradi, bunda SimpleType ning metod jadvalidagi ikkala yozuv ham bir xil amalga oshirishga ishora qilishi kerakligini bildiradi. Buni yanada aniqroq qilish uchun, mana klassning umumiy Dispose metodini hamda klassning IDisposable ning Dispose metodini amalga oshirishini chaqirishni ko'rsatadigan kod:

public sealed class Program {
    public static void Main() {
        SimpleType st = new SimpleType();

        // Bu umumiy Dispose metod amalga oshirilishini chaqiradi
        st.Dispose();

        // Bu IDisposable ning Dispose metod amalga oshirilishini chaqiradi
        IDisposable d = st;
        d.Dispose();
    }
}

Birinchi Dispose chaqiruvida SimpleType tomonidan aniqlangan Dispose metodi chaqiriladi. Keyin men IDisposable interfeys turiga ega d o'zgaruvchisini aniqlayab, d ni SimpleType obyektiga ishora qilish uchun initsializatsiya qilaman. Endi d.Dispose() ni chaqirganimda, men IDisposable interfeysining Dispose metodini chaqiryapman. C# umumiy Dispose metodining IDisposable ning Dispose metodi uchun ham amalga oshirish bo'lishini talab qilganligi sababli, bir xil kod bajariladi, va bu misolda hech qanday sezilarli farq ko'rmaysiz. Natija quyidagicha:

Dispose
Dispose

Endi, SimpleType ni qayta yozib, sezilarli farqni ko'rsataman:

internal sealed class SimpleType : IDisposable {
    public void Dispose() { Console.WriteLine("public Dispose"); }
    void IDisposable.Dispose() { Console.WriteLine("IDisposable Dispose"); }
}

Oldingi Main metodini o'zgartirmasdan, shunchaki qayta kompilyatsiya qilib dasturni ishga tushirsak, natija quyidagicha bo'ladi:

public Dispose
IDisposable Dispose

C# da metod nomini interfeys nomini aniqlash bilan oldindan belgilaganingizda (masalan, IDisposable.Dispose), siz aniq interfeys metod amalga oshirilishi (explicit interface method implementation, qisqacha EIMI) yaratyapsiz. E'tibor bering, C# da aniq interfeys metodi aniqlanganida, siz biron-bir kirish darajasini (public yoki private kabi) belgilash imkoni yo'q. Ammo, kompilyator metod uchun metama'lumotlarni yaratganida, uning kirish darajasi private ga o'rnatiladi, bu klassning instansiyasidan foydalanib interfeys metodini to'g'ridan-to'g'ri chaqirishga to'sqinlik qiladi. Interfeys metodini chaqirishning yagona usuli — interfeys turining o'zgaruvchisi orqali.

Shuningdek, EIMI virtual deb belgilanishi va shuning uchun override qilinishi mumkin emasligiga e'tibor bering. Buning sababi shundaki, EIMI aslida turning obyekt modelining bir qismi emas; u turga interfeys (xatti-harakatlar yoki metodlar to'plami) ni tur uchun shaffof qilmasdan biriktirish usuli. Agar bularning barchasi sizga biroz noqulay ko'rinsa, siz to'g'ri tushunmoqdasiz — bularning barchasi haqiqatdan ham noqulay. Ushbu bobda keyinroq men EIMI lardan foydalanishning ba'zi haqiqiy sabablarini ko'rsataman.

Generik Interfeyslar

C# va CLR ning generik interfeyslarni qo'llab-quvvatlashi dasturchilar uchun ko'plab ajoyib xususiyatlarni taqdim etadi. Ushbu bo'limda men generik interfeyslardan foydalanganda taklif qilinadigan afzalliklarni muhokama qilmoqchiman.

Birinchidan, generik interfeyslar ajoyib kompilyatsiya vaqtidagi tur xavfsizligini taklif qiladi. Ba'zi interfeyslar (masalan, generik bo'lmagan IComparable interfeysi) Object parametrlari yoki qaytarish turlariga ega metodlarni aniqlaydi. Kod bu interfeys metodlarini chaqirganida, istalgan turning instansiyasiga havola uzatilishi mumkin. Lekin bu odatda istalmaydi. Quyidagi kod buni ko'rsatadi:

private void SomeMethod1() {
    Int32 x = 1, y = 2;
    IComparable c = x;

    // CompareTo Object kutadi; y (Int32) ni uzatish OK
    c.CompareTo(y);     // y bu yerda boxed bo'ladi

    // CompareTo Object kutadi; "2" (String) ni uzatish kompilyatsiya qilinadi
    // lekin ish vaqtida ArgumentException otiladi
    c.CompareTo("2");
}

Interfeys metodini kuchli turlangan (strongly typed) qilish afzalroq, shuning uchun FCL generik IComparable<in T> interfeysini o'z ichiga oladi.

Mana kodni generik interfeys yordamida qayta yozilgan versiyasi:

private void SomeMethod2() {
    Int32 x = 1, y = 2;
    IComparable<Int32> c = x;

    // CompareTo Int32 kutadi; y (Int32) ni uzatish OK
    c.CompareTo(y);     // y bu yerda boxed BO'LMAYDI

    // CompareTo Int32 kutadi; "2" (String) ni uzatish natijasida
    // kompilyator xatosi paydo bo'ladi, chunki String ni Int32 ga
    // o'tkazib bo'lmaydi
    c.CompareTo("2");   // Xato
}

Ikkinchidan, generik interfeyslar value turlar bilan ishlanganda ancha kamroq boxing sodir bo'lishini ta'minlaydi. SomeMethod1 dagi generik bo'lmagan IComparable interfeysining CompareTo metodi Object kutadi; y (Int32 value tur) ni uzatish y dagi qiymatni boxed bo'lishiga olib keladi. Ammo, SomeMethod2 dagi generik IComparable<Int32> interfeysining CompareTo metodi Int32 kutadi; y ni qiymat bo'yicha uzatadi va hech qanday boxing kerak emas.

Eslatma

FCL IComparable, ICollection, IList va IDictionary interfeyslarining generik bo'lmagan va generik versiyalarini aniqlaydi, shuningdek ba'zi boshqalarni ham. Agar siz turni aniqlayotgan bo'lsangiz va bu interfeyslarning birortasini amalga oshirmoqchi bo'lsangiz, odatda interfeyslarning generik versiyalarini amalga oshirishingiz kerak. Generik bo'lmagan versiyalar FCL da .NET Framework generiklar qo'llab-quvvatlashidan oldin yozilgan kod bilan orqaga moslik uchun mavjud. Generik bo'lmagan versiyalar foydalanuvchilarga ma'lumotlarni umumiyroq, kamroq tur-xavfsiz usulda boshqarish imkonini beradi.

Ba'zi generik interfeyslar generik bo'lmagan versiyalarini meros oladi, shuning uchun sizning klassingiz interfeyslarning ham generik, ham generik bo'lmagan versiyalarini amalga oshirishi kerak bo'ladi. Masalan, generik IEnumerable<out T> interfeysi generik bo'lmagan IEnumerable interfeysini meros oladi. Shuning uchun agar sizning klassingiz IEnumerable<out T> ni amalga oshirsa, sizning klassingiz IEnumerable ni ham amalga oshirishi kerak.

Ba'zan boshqa kod bilan integratsiyalashganingizda, generik bo'lmagan interfeysni amalga oshirishga to'g'ri kelishi mumkin, chunki interfeysning generik versiyasi mavjud emas. Bunday holda, agar interfeys metodlari Object turli parametrlarni qabul qilsa yoki qaytarsa, kompilyatsiya vaqtidagi tur xavfsizligini yo'qotasiz va value turlar bilan boxing paydo bo'ladi. Ushbu bobning oxiridagi "EIMI Bilan Kompilyatsiya Vaqtida Tur Xavfsizligini Yaxshilash" bo'limida tavsiflangan texnika yordamida bu holatni biroz yengillashtirish mumkin.

Uchinchidan, generik interfeyslar klassga bir xil interfeysni turli tur parametrlari ishlatilsa, bir necha marta amalga oshirishga imkon beradi.

Quyidagi kod bu qanchalik foydali bo'lishini ko'rsatadigan misol:

using System;

// Bu klass generik IComparable<T> interfeysini ikki marta amalga oshiradi
public sealed class Number : IComparable<Int32>, IComparable<String> {
    private Int32 m_val = 5;

    // Bu metod IComparable<Int32> ning CompareTo sini amalga oshiradi
    public Int32 CompareTo(Int32 n) {
        return m_val.CompareTo(n);
    }

    // Bu metod IComparable<String> ning CompareTo sini amalga oshiradi
    public Int32 CompareTo(String s) {
        return m_val.CompareTo(Int32.Parse(s));
    }
}

public static class Program {
    public static void Main() {
        Number n = new Number();

        // Bu yerda men n dagi qiymatni Int32 (5) bilan solishtiryapman
        IComparable<Int32> cInt32 = n;
        Int32 result = cInt32.CompareTo(5);

        // Bu yerda men n dagi qiymatni String ("5") bilan solishtiryapman
        IComparable<String> cString = n;
        result = cString.CompareTo("5");
    }
}

Interfeys generik tur parametrlari kontravariant (contra-variant) va kovariant (covariant) deb ham belgilanishi mumkin, bu generik interfeyslardan foydalanishda yanada ko'proq moslashuvchanlik beradi. Kontravariantlik va kovariantlik haqida batafsil ma'lumot uchun 12-bobdagi "Delegat va Interfeys Kontravariant va Kovariant Generik Tur Argumentlari" bo'limiga qarang.

Generiklar va Interfeys Cheklovlari

Oldingi bo'limda men generik interfeyslardan foydalanishning afzalliklarini muhokama qildim. Ushbu bo'limda men generik tur parametrlarini interfeyslarga cheklashning (constraining) afzalliklarini muhokama qilaman.

Birinchi afzallik shundaki, siz yagona generik tur parametrini bir nechta interfeyslarga cheklay olasiz. Buni qilganingizda, uzatayotgan parametringizning turi barcha interfeys cheklovlarini amalga oshirishi kerak.

Mana misol:

public static class SomeType {
    private static void Test() {
        Int32 x = 5;
        Guid g = new Guid();

        // M ga bu chaqiruv muvaffaqiyatli kompilyatsiya qilinadi,
        // chunki Int32 IComparable VA IConvertible ni amalga oshiradi
        M(x);

        // M ga bu chaqiruv kompilyator xatosiga olib keladi,
        // chunki Guid IComparable ni amalga oshiradi, lekin
        // IConvertible ni amalga oshirmaydi
        M(g);
    }

    // M ning tur parametri T faqat IComparable VA IConvertible
    // interfeyslarini amalga oshiradigan turlar bilan ishlashga cheklangan
    private static Int32 M<T>(T t) where T : IComparable, IConvertible {
        ...
    }
}

Bu aslida juda ajoyib! Metodning parametrlarini aniqlashda har bir parametr turi ko'rsatiladi, bu parametrning turida yoki undan hosil bo'lgan turda bo'lishi kerakligini bildiradi. Agar parametr turi interfeys bo'lsa, bu argumentning klass interfeysi amalga oshirgan istalgan klass turiga ega bo'lishi mumkinligini bildiradi. Bir nechta interfeys cheklovlarini ishlatish aslida metodga argument sifatida uzatiladigan tur bir nechta interfeyslarni amalga oshirishi kerakligini bildirishi imkonini beradi.

Aslida, agar biz T ni klass va ikkita interfeysga cheklagan bo'lsak, biz uzatiladigan argumentning turi belgilangan bazaviy klass turida bo'lishi (yoki undan hosil bo'lishi) kerak va u ikkita interfeysni ham amalga oshirishi kerakligini aytgan bo'lamiz. Bu moslashuvchanlik metodga chaqiruvchilarning nima uzatishi mumkinligini haqiqatan ham belgilash imkonini beradi va chaqiruvchilar ushbu cheklovlarni bajarmasa, kompilyator xatolari yuzaga keladi.

Ikkinchi afzallik shundaki, interfeys cheklovlari value turlarning instansiyalari uzatilganda boxing ni kamaytiradi. Oldingi kod fragmentida M metodiga x (Int32 ning instansiyasi, ya'ni value tur) uzatildi. x M ga uzatilganda hech qanday boxing sodir bo'lmaydi. Agar M ichidagi kod t.CompareTo(...) ni chaqirsa, hali ham hech qanday boxing sodir bo'lmaydi (garchi CompareTo ga uzatiladigan argumentlar uchun boxing sodir bo'lishi mumkin).

Boshqa tomondan, agar M quyidagicha e'lon qilingan bo'lsa:

private static Int32 M(IComparable t) {
    ...
}

unda x ni M ga uzatish uchun x boxed bo'lishi kerak edi.

Interfeys cheklovlari uchun C# kompilyatori value tur ustida interfeys metodini to'g'ridan-to'g'ri boxing qilmasdan chaqirishga olib keladigan maxsus Intermediate Language (IL) ko'rsatmalarini chiqaradi. Interfeys cheklovlarini ishlatishdan tashqari, C# kompilyatorining bu IL ko'rsatmalarini chiqarishining boshqa yo'li yo'q, shuning uchun value tur ustida interfeys metodini chaqirish har doim boxing ga olib keladi.

Bir Xil Metod Nomi va Signaturasiga Ega Bir Nechta Interfeyslarni Amalga Oshirish

Ba'zan siz bir xil nom va signaturaga ega metodlarni aniqlagan bir nechta interfeyslarni amalga oshiradigan turni aniqlashingiz mumkin. Masalan, ikkita interfeys quyidagicha aniqlangan deb tasavvur qiling:

public interface IWindow {
    Object GetMenu();
}

public interface IRestaurant {
    Object GetMenu();
}

Aytaylik, siz ushbu interfeyslarning ikkalasini ham amalga oshiradigan tur yaratmoqchisiz. Turning a'zolarini aniq interfeys metod amalga oshirishlari (EIMI) yordamida amalga oshirishingiz kerak bo'ladi, quyidagi tarzda:

// Bu tur System.Object dan hosil bo'ladi va
// IWindow va IRestaurant interfeyslarini amalga oshiradi.
public sealed class MarioPizzeria : IWindow, IRestaurant {

    // Bu IWindow ning GetMenu metodi uchun amalga oshirish.
    Object IWindow.GetMenu() { ... }

    // Bu IRestaurant ning GetMenu metodi uchun amalga oshirish.
    Object IRestaurant.GetMenu() { ... }

    // Bu (ixtiyoriy) metod interfeysga aloqasi yo'q bo'lgan
    // oddiy GetMenu metodi.
    public Object GetMenu() { ... }
}

Bu tur bir nechta alohida GetMenu metodlarini amalga oshirishi kerak bo'lganligi sababli, siz C# kompilyatoriga qaysi GetMenu metodi ma'lum bir interfeys uchun amalga oshirilishini aytishingiz kerak. Quyidagi kod MarioPizzeria obyektini ishlatadigan va kerakli metodni chaqiradigan kodni ko'rsatadi:

MarioPizzeria mp = new MarioPizzeria();

// Bu satr MarioPizzeria ning public GetMenu metodini chaqiradi
mp.GetMenu();

// Bu satrlar MarioPizzeria ning IWindow GetMenu metodini chaqiradi
IWindow window = mp;
window.GetMenu();

// Bu satrlar MarioPizzeria ning IRestaurant GetMenu metodini chaqiradi
IRestaurant restaurant = mp;
restaurant.GetMenu();

Aniq Interfeys Metod Amalga Oshirishlari Bilan Kompilyatsiya Vaqtida Tur Xavfsizligini Yaxshilash

Interfeyslar ajoyib, chunki ular turlar o'zaro muloqot qilishi uchun standart usulni belgilaydi. Oldinroq men generik interfeyslar va ular kompilyatsiya vaqtida tur xavfsizligini qanday yaxshilashi va boxing ni kamaytirishi haqida gaplashdim. Afsuski, ba'zan generik bo'lmagan interfeysni amalga oshirishga to'g'ri kelishi mumkin, chunki generik versiyasi mavjud emas. Agar interfeysning metod(lar)i System.Object turidagi parametrlarni qabul qilsa yoki System.Object turidagi qiymat qaytarsa, kompilyatsiya vaqtidagi tur xavfsizligini yo'qotasiz va boxing sodir bo'ladi. Ushbu bo'limda men sizga EIMI yordamida bu holatni biroz yaxshilashni ko'rsataman.

Juda keng tarqalgan IComparable interfeysiga qarang:

public interface IComparable {
    Int32 CompareTo(Object other);
}

Bu interfeys System.Object turidagi parametrni qabul qiladigan bitta metodni aniqlaydi. Agar men ushbu interfeysni amalga oshiradigan o'z turni aniqlasam, tur ta'rifi quyidagicha ko'rinishi mumkin:

internal struct SomeValueType : IComparable {
    private Int32 m_x;
    public SomeValueType(Int32 x) { m_x = x; }
    public Int32 CompareTo(Object other) {
        return(m_x - ((SomeValueType) other).m_x);
    }
}

SomeValueType dan foydalanib, men quyidagi kodni yozishim mumkin:

public static void Main() {
    SomeValueType v = new SomeValueType(0);
    Object o = new Object();
    Int32 n = v.CompareTo(v); // Istalmagan boxing
    n = v.CompareTo(o);       // InvalidCastException
}

Bu kodda ikkita muammo bor:

  • Istalmagan boxingv CompareTo metodiga argument sifatida uzatilganda, u boxed bo'lishi kerak, chunki CompareTo Object ni kutadi.
  • Tur xavfsizligi yo'qligi — Bu kod kompilyatsiya qilinadi, lekin CompareTo metodi ichida o ni SomeValueType ga o'tkazishga uringanida InvalidCastException otiladi.

Ikkala muammoni ham EIMI yordamida hal qilish mumkin. Mana SomeValueType ning EIMI qo'shilgan o'zgartirilgan versiyasi:

internal struct SomeValueType : IComparable {
    private Int32 m_x;
    public SomeValueType(Int32 x) { m_x = x; }

    public Int32 CompareTo(SomeValueType other) {
        return(m_x - other.m_x);
    }

    // ESLATMA: Keyingi satrda public/private yo'q
    Int32 IComparable.CompareTo(Object other) {
        return CompareTo((SomeValueType) other);
    }
}

Ushbu yangi versiyada bir nechta o'zgarishlarga e'tibor bering. Birinchidan, endi ikkita CompareTo metodi bor. Birinchi CompareTo metodi endi Object o'rniga SomeValueType ni parametr sifatida qabul qiladi. Bu parametr o'zgarganligi sababli, other ni SomeValueType ga o'tkazadigan (cast) kod endi kerak emas va o'chirilgan. Ikkinchidan, birinchi CompareTo metodini tur-xavfsiz qilish uchun o'zgartirish SomeValueType IComparable interfeysi qo'ygan shartnomaga endi mos kelmasligini anglatadi. Shuning uchun SomeValueType IComparable shartnomani qondiradigan CompareTo metodini amalga oshirishi kerak. Bu ikkinchi IComparable.CompareTo metodi — EIMI ning vazifasi.

Ushbu ikkita o'zgarishni qilgandan so'ng, biz endi kompilyatsiya vaqtida tur xavfsizligiga va boxing yo'qligiga erishamiz:

public static void Main() {
    SomeValueType v = new SomeValueType(0);
    Object o = new Object();
    Int32  n = v.CompareTo(v); // Boxing yo'q
    n = v.CompareTo(o);        // kompilyatsiya vaqti xatosi
}

Ammo, agar biz interfeys turining o'zgaruvchisini aniqlasak, kompilyatsiya vaqtidagi tur xavfsizligini yo'qotamiz va yana istalmagan boxing paydo bo'ladi:

public static void Main() {
    SomeValueType v = new SomeValueType(0);
    IComparable c = v;             // Boxing!

    Object o = new Object();
    Int32  n = c.CompareTo(v);    // Istalmagan boxing
    n = c.CompareTo(o);           // InvalidCastException
}

Aslida, ushbu bobda oldinroq aytib o'tilganidek, value tur instansiyasini interfeys turiga o'tkazganingizda, CLR value tur instansiyasini boxed qilishi kerak. Shu sababli oldingi Main metodida ikkita boxing sodir bo'ladi.

EIMI lar IConvertible, ICollection, IList va IDictionary kabi interfeyslarni amalga oshirishda tez-tez ishlatiladi. Ular sizga ushbu interfeyslarning metodlarining tur-xavfsiz versiyalarini yaratish va value turlar uchun boxing operatsiyalarini kamaytirish imkonini beradi.

Aniq Interfeys Metod Amalga Oshirishlari Bilan Ehtiyot Bo'ling

EIMI lardan foydalanganingizda mavjud bo'lgan ba'zi oqibatlarni tushunish juda muhimdir. Va shu oqibatlar tufayli, siz imkon qadar EIMI lardan qochishga harakat qilishingiz kerak. Yaxshiyamki, generik interfeyslar sizga EIMI lardan anchagina qochishga yordam beradi. Ammo ba'zan ulardan foydalanishga to'g'ri keladigan paytlar ham bo'lishi mumkin (masalan, bir xil nom va signaturaga ega ikkita interfeys metodini amalga oshirish). Mana EIMI larning katta muammolari:

  • Turning EIMI metodini qanday amalga oshirishini tushuntiradigan hech qanday hujjat yo'q va Microsoft Visual Studio IntelliSense qo'llab-quvvatlashi ham yo'q.
  • Value tur instansiyalari interfeysga o'tkazilganda (cast qilinganda) boxed bo'ladi.
  • EIMI ni hosil bo'lgan (derived) tur chaqira olmaydi.

Keling, bu muammolarni batafsil ko'rib chiqaylik.

Hujjat Yo'qligi va IntelliSense Muammosi

.NET Framework ma'lumotnomasi hujjatlarida turning metodlarini ko'rsangiz, aniq interfeys metod amalga oshirishlari ro'yxatga olingan, lekin turga xos yordam mavjud emas; siz shunchaki interfeys metodlari haqidagi umumiy yordamni o'qishingiz mumkin. Masalan, Int32 turi uchun hujjat u IConvertible interfeysining barcha metodlarini amalga oshirishini ko'rsatadi. Bu yaxshi, chunki dasturchilar bu metodlar borligini biladi; ammo, bu dasturchilarni juda chalkashtirib yuboradi, chunki siz IConvertible metodini Int32 ustida to'g'ridan-to'g'ri chaqira olmaysiz. Masalan, quyidagi metod kompilyatsiya qilinmaydi:

public static void Main() {
    Int32 x = 5;
    Single s = x.ToSingle(null); // IConvertible metodini chaqirishga urinish
}

Bu metodni kompilyatsiya qilganingizda, C# kompilyatori quyidagi xabar beradi: 'int' 'ToSingle' uchun ta'rif o'z ichiga olmaydi. Bu xato xabari dasturchi uchun chalg'ituvchi, chunki u Int32 turi ToSingle metodini aniqlamaydi deb aytadi, holbuki aslida aniqlaydi.

Int32 ustida ToSingle ni chaqirish uchun, avval Int32 ni IConvertible ga o'tkazish kerak, quyidagi tarzda:

public static void Main() {
    Int32 x = 5;
    Single s = ((IConvertible) x).ToSingle(null);
}

Bu o'tkazish (cast) umuman aniq emas va ko'plab dasturchilar buni o'zlari topa olmaydi. Lekin bundan ham kattaroq muammo bor: Int32 value turini IConvertible ga o'tkazish shuningdek value turni boxed qiladi, xotirani sarflaydi va ishlashga zarar yetkazadi. Bu ushbu bo'limning boshida aytib o'tgan ikkita katta muammoning ikkinchisidir.

Voris Klasslar Muammosi

EIMI lardagi uchinchi va ehtimol eng katta muammo shundaki, ularni hosil bo'lgan (derived) tur chaqira olmaydi. Mana misol:

internal class Base : IComparable {

    // Aniq Interfeys Metod Amalga Oshirilishi
    Int32 IComparable.CompareTo(Object o) {
        Console.WriteLine("Base's CompareTo");
        return 0;
    }
}

internal sealed class Derived : Base, IComparable {

    // Public metod va interfeysni amalga oshirish ham
    public Int32 CompareTo(Object o) {
        Console.WriteLine("Derived's CompareTo");

        // Bazaviy klassning EIMI sini chaqirishga urinish
        // kompilyator xatosiga olib keladi:
        // error CS0117: 'Base' 'CompareTo' uchun ta'rif o'z ichiga olmaydi
        base.CompareTo(o);
        return 0;
    }
}

Derived ning CompareTo metodida men base.CompareTo ni chaqirishga harakat qilyapman, lekin bu C# kompilyatorining xatolik berishiga olib keladi. Muammo shundaki, Base klassi umumiy (public) yoki himoyalangan (protected) CompareTo metodini taklif qilmaydi; u faqat IComparable turning o'zgaruvchisi orqali chaqirilishi mumkin bo'lgan CompareTo metodini taklif qiladi.

Men Derived ning CompareTo metodini quyidagicha o'zgartirishim mumkin:

// Public metod va interfeysni amalga oshirish ham
public Int32 CompareTo(Object o) {
    Console.WriteLine("Derived's CompareTo");

    // Bazaviy klassning EIMI sini chaqirishga urinish
    // cheksiz rekursiyaga olib keladi
    IComparable c = this;
    c.CompareTo(o);

    return 0;
}

Bu versiyada men this ni IComparable o'zgaruvchisiga o'tkazyapman, c. Va keyin, men c dan foydalanib CompareTo ni chaqiryapman. Ammo, Derived ning umumiy CompareTo metodi Derived ning IComparable.CompareTo metodi uchun amalga oshirish sifatida xizmat qiladi, va shuning uchun cheksiz rekursiya sodir bo'ladi. Bu muammoni Derived klassini IComparable interfeysisiz e'lon qilish orqali hal qilish mumkin:

internal sealed class Derived : Base /*, IComparable */ { ... }

Endi oldingi CompareTo metodi Base dagi CompareTo metodini chaqiradi. Lekin ba'zan siz interfeysni turdan shunchaki olib tashlay olmaysiz, chunki siz hosil bo'lgan tur interfeysni amalga oshirishini xohlaysiz. Buni to'g'ri hal qilishning eng yaxshi usuli — bazaviy klass aniq amalga oshirgan interfeys metodiga qo'shimcha ravishda virtual metod taqdim etishi. Keyin Derived klassi virtual metodni override qilishi mumkin. Mana Base va Derived klasslarini aniqlashning to'g'ri usuli:

internal class Base : IComparable {

    // Aniq Interfeys Metod Amalga Oshirilishi
    Int32 IComparable.CompareTo(Object o) {
        Console.WriteLine("Base's IComparable CompareTo");
        return CompareTo(o);   // Bu endi virtual metodni chaqiradi
    }

    // Hosil bo'lgan klasslar uchun virtual metod
    // (bu metod istalgan nomga ega bo'lishi mumkin)
    public virtual Int32 CompareTo(Object o) {
        Console.WriteLine("Base's virtual CompareTo");
        return 0;
    }
}

internal sealed class Derived : Base, IComparable {

    // Public metod va interfeysni amalga oshirish ham
    public override Int32 CompareTo(Object o) {
        Console.WriteLine("Derived's CompareTo");

        // Endi biz Base ning virtual metodini chaqira olamiz
        return base.CompareTo(o);
    }
}

E'tibor bering, men yuqoridagi virtual metodni umumiy metod sifatida aniqladim, lekin ba'zi hollarda siz metodni himoyalangan (protected) qilishni afzal ko'rasiz. Virtual metodni public o'rniga protected qilish yaxshi, lekin bu ba'zi boshqa kichik o'zgarishlarni talab qiladi. Bu muhokama EIMI lar ehtiyotkorlik bilan ishlatilishi kerakligini aniq ko'rsatadi. Ko'plab dasturchilar EIMI lar haqida birinchi marta o'rganganlarida, ular ajoyib deb o'ylashadi va ularni imkon qadar ko'proq ishlata boshlashadi. Bunday qilmang! EIMI lar ba'zi holatlarda foydali, lekin ulardan imkon qadar qochish kerak, chunki ular turdan foydalanishni ancha qiyinlashtiradi.

Loyihalash: Bazaviy Klass yoki Interfeys?

Men ko'pincha quyidagi savolni eshitaman: "Men bazaviy turni mi yoki interfeysni loyihalashim kerak?" Javob har doim aniq emas. Mana sizga yordam beradigan ba'zi ko'rsatmalar:

  • IS-A va CAN-DO munosabati — Tur faqat bitta amalga oshirishdan meros olishi mumkin. Agar hosil bo'lgan tur bazaviy tur bilan IS-A (bu shunday) munosabatini da'vo qila olmasa, bazaviy turni ishlatmang; interfeysdan foydalaning. Interfeyslar CAN-DO (buni qila oladi) munosabatini bildiradi. Agar CAN-DO funksionallik turli obyekt turlariga tegishli ko'rinsa, interfeysdan foydalaning. Masalan, tur o'zining instansiyalarini boshqa turga o'zgartira oladi (IConvertible), tur o'zining instansiyasini seriyalash mumkin (ISerializable) va hokazo. E'tibor bering, value turlar System.ValueType dan hosil bo'lishi kerak va shuning uchun ixtiyoriy bazaviy klassdan hosil bo'la olmaydi. Bunday holda siz CAN-DO munosabatini ishlatishingiz va interfeysni aniqlashingiz kerak.
  • Foydalanish qulayligi — Dasturchi sifatida siz uchun interfeysning barcha metodlarini amalga oshirishdan ko'ra, bazaviy klassdan hosil bo'lgan yangi turni aniqlash odatda osonroq. Bazaviy tur ko'p funksionallikni taqdim qilishi mumkin, shuning uchun hosil bo'lgan tur ehtimol o'z xatti-harakatiga faqat nisbatan kichik o'zgartirishlar kiritishi kerak. Agar siz interfeysni taqdim qilsangiz, yangi tur barcha a'zolarni amalga oshirishi kerak.
  • Izchil amalga oshirish — Interfeys shartnomasi qanchalik yaxshi hujjatlashtirilgan bo'lishidan qat'i nazar, hamma shartnomani 100 foiz to'g'ri amalga oshirishi juda ehtimoldan yiroq. Aslida, COM aynan shu muammoga duch keladi, shuning uchun ba'zi COM obyektlari faqat Microsoft Word yoki Windows Internet Explorer bilan to'g'ri ishlaydi. Yaxshi standart amalga oshirishga ega bazaviy turni taqdim qilish orqali, siz ishlaydigan va yaxshi sinovdan o'tgan turdan foydalanishni boshlaysiz; keyin o'zgartirish kerak bo'lgan qismlarni o'zgartirasiz.
  • Versiyalash — Agar siz bazaviy turga yangi metod qo'shsangiz, hosil bo'lgan tur yangi metodni meros oladi, siz ishlaydigan turdan foydalanishni boshlaysiz va foydalanuvchining manba kodini qayta kompilyatsiya qilish shart emas. Interfeysga yangi a'zo qo'shish interfeysni meros oluvchini o'z manba kodini o'zgartirishga va qayta kompilyatsiya qilishga majbur qiladi.

FCL da oqimli ma'lumotlarni uzatish bilan bog'liq klasslar amalga oshirish merosxo'rligi loyihasidan foydalanadi. System.IO.Stream klassi abstrakt bazaviy klassdir. U bir qancha metodlarni taqdim etadi, masalan Read va Write. Boshqa klasslar — System.IO.FileStream, System.IO.MemoryStream va System.Net.Sockets.NetworkStreamStream dan hosil bo'lgan. Microsoft ushbu uchta klass va Stream klassi o'rtasida IS-A munosabatini tanladi, chunki bu konkret klasslarni amalga oshirishni osonlashtirdi. Masalan, hosil bo'lgan klasslar faqat sinxron I/O operatsiyalarini amalga oshirishi kerak; ular asinxron I/O operatsiyalarini bajarish qobiliyatini Stream bazaviy klassidan meros oladi.

Tan olish kerak, oqim klasslari uchun merosxo'rlikdan foydalanishni tanlash butunlay aniq emas; Stream bazaviy klassi aslida juda kam amalga oshirishni taqdim etadi. Ammo, agar siz Windows Forms boshqaruv elementlari klasslarini ko'rsangiz, unda Button, CheckBox, ListBox va boshqa barcha boshqaruv elementlari System.Windows.Forms.Control dan hosil bo'lgan, Control amalga oshiradigan barcha kodni tasavvur qilish oson va turli boshqaruv elementlari to'g'ri ishlashi uchun shunchaki meros oladi.

Aksincha, Microsoft FCL to'plamlarini interfeysga asoslangan holda loyihaladi. System.Collections.Generic nomlar fazosi bir nechta to'plam bilan bog'liq interfeyslarni aniqlaydi: IEnumerable<out T>, ICollection<T>, IList<T> va IDictionary<TKey, TValue>. Keyin Microsoft bir qancha klasslarni taqdim etdi, masalan List<T>, Dictionary<TKey, TValue>, Queue<T>, Stack<T> va boshqalar, ular ushbu interfeyslarning kombinatsiyalarini amalga oshiradi. Bu yerda loyihachilar klasslar va interfeyslar o'rtasida CAN-DO munosabatini tanladilar, chunki turli to'plam klasslarining amalga oshirishlari bir-biridan tubdan farq qiladi. Boshqacha aytganda, List<T>, Dictionary<TKey, TValue> va Queue<T> o'rtasida bo'lishish mumkin bo'lgan kod juda kam.

Bu to'plam klasslari taklif qiladigan operatsiyalar, shunga qaramay, juda izchil. Masalan, ularning barchasi sanab o'tish (enumerate) mumkin bo'lgan elementlar to'plamini saqlaydi va ularning barchasi elementlarni qo'shish va o'chirishga ruxsat beradi. Agar sizda turi IList<T> interfeysini amalga oshiradigan obyektga havola bo'lsa, siz element qo'shish, o'chirish va elementni qidirish uchun kod yozishingiz mumkin — siz qanday tur to'plam bilan ishlayotganingizni aniq bilmasligingiz ham mumkin. Bu juda kuchli mexanizm.

Nihoyat, siz aslida ikkalasini ham qilishingiz mumkin: interfeys aniqlash va interfeysni amalga oshiradigan bazaviy klass taqdim etish. Masalan, FCL IComparer<in T> interfeysini aniqlaydi va istalgan tur bu interfeysni amalga oshirishni tanlashi mumkin. Bundan tashqari, FCL Comparer<T> abstrakt bazaviy klassini taqdim etadi, bu interfeys amalga oshiradi va generik bo'lmagan IComparer ning Compare metodi uchun standart amalga oshirishni taqdim etadi. Ham interfeys ta'rifi, ham bazaviy klass mavjud bo'lishi katta moslashuvchanlik beradi, chunki dasturchilar endi xohlaganini tanlashi mumkin.