Bagikan melalui


Kelas Generik (C++/CLI)

Kelas generik dinyatakan menggunakan formulir berikut:

Sintaks

[attributes]
generic <class-key type-parameter-identifier(s)>
[constraint-clauses]
[accessibility-modifiers] ref class identifier  [modifiers]
[: base-list]
{
class-body
} [declarators] [;]

Keterangan

Dalam sintaks di atas, istilah berikut digunakan:

Atribut
(Opsional) Informasi deklaratif tambahan. Untuk informasi selengkapnya tentang atribut dan kelas atribut, lihat Atribut.

kunci kelas
Baik class atau typename

type-parameter-identifier, daftar pengidentifikasi yang dipisahkan koma yang menentukan nama parameter jenis.

klausa batasan
Daftar (tidak dipisahkan koma) dari klausul di mana menentukan batasan untuk parameter jenis. Mengambil formulir:

wheretype-parameter-identifier:constraint-list...

daftar batasan
class-or-interface[,...]

pengubah aksesibilitas
Pengubah aksesibilitas untuk kelas generik. Untuk Windows Runtime, satu-satunya pengubah yang diizinkan adalah private. Untuk runtime bahasa umum, pengubah yang diizinkan adalah private dan public.

identifier
Nama kelas generik, pengidentifikasi C++ yang valid.

Pengubah
(Opsional) Pengubah yang diizinkan termasuk disegel dan abstrak.

daftar dasar
Daftar yang berisi satu kelas dasar dan antarmuka apa pun yang diimplementasikan, semuanya dipisahkan oleh koma.

isi kelas
Isi kelas, berisi bidang, fungsi anggota, dll.

deklarator
Deklarasi variabel apa pun dari jenis ini. Misalnya: ^pengidentifikasi[, ...]

Anda dapat mendeklarasikan kelas generik seperti ini (perhatikan bahwa kata kunci class dapat digunakan alih-alih typename). Dalam contoh ini, ItemType, KeyType dan ValueType merupakan jenis yang tidak diketahui yang ditentukan pada titik di mana jenisnya. HashTable<int, int> adalah jenis yang dibangun dari jenis HashTable<KeyType, ValueType>generik . Sejumlah jenis konstruksi yang berbeda dapat dibangun dari satu jenis generik. Jenis yang dibangun yang dibangun dari kelas generik diperlakukan seperti jenis kelas ref lainnya.

// generic_classes_1.cpp
// compile with: /clr
using namespace System;
generic <typename ItemType>
ref struct Stack {
   // ItemType may be used as a type here
   void Add(ItemType item) {}
};

generic <typename KeyType, typename ValueType>
ref class HashTable {};

// The keyword class may be used instead of typename:
generic <class ListItem>
ref class List {};

int main() {
   HashTable<int, Decimal>^ g1 = gcnew HashTable<int, Decimal>();
}

Kedua jenis nilai (baik jenis bawaan seperti int atau double, atau jenis nilai yang ditentukan pengguna) dan jenis referensi dapat digunakan sebagai argumen jenis generik. Sintaks dalam definisi generik sama terlepas dari itu. Secara sintetis, jenis yang tidak diketahui diperlakukan seolah-olah itu adalah jenis referensi. Namun, runtime dapat menentukan bahwa jika jenis yang benar-benar digunakan adalah jenis nilai dan mengganti kode yang dihasilkan yang sesuai untuk akses langsung ke anggota. Jenis nilai yang digunakan sebagai argumen jenis generik tidak dikotak dan jadi jangan menderita penalti performa yang terkait dengan tinju. Sintaks yang digunakan dalam isi generik harus T^ dan -> bukan .. Setiap penggunaan ref baru, gcnew untuk parameter jenis akan ditafsirkan dengan tepat oleh runtime sebagai pembuatan sederhana jenis nilai jika argumen jenis adalah jenis nilai.

Anda juga dapat mendeklarasikan kelas generik dengan Batasan pada Parameter Jenis Generik (C++/CLI) pada jenis yang dapat digunakan untuk parameter jenis. Dalam contoh berikut, jenis apa pun yang digunakan untuk ItemType harus mengimplementasikan IItem antarmuka. Mencoba menggunakan int, misalnya, yang tidak menerapkan IItem, akan menghasilkan kesalahan waktu kompilasi karena argumen jenis tidak memenuhi batasan.

// generic_classes_2.cpp
// compile with: /clr /c
interface class IItem {};
generic <class ItemType>
where ItemType : IItem
ref class Stack {};

Kelas generik di namespace yang sama tidak dapat dimuat berlebihan hanya dengan mengubah angka atau jenis parameter jenis. Namun, jika setiap kelas tinggal di namespace yang berbeda, mereka dapat kelebihan beban. Misalnya, pertimbangkan dua kelas berikut, MyClass dan MyClass<ItemType>, di namespace A layanan dan B. Kedua kelas kemudian dapat kelebihan beban di namespace ketiga C:

// generic_classes_3.cpp
// compile with: /clr /c
namespace A {
   ref class MyClass {};
}

namespace B {
   generic <typename ItemType>
   ref class MyClass2 { };
}

namespace C {
   using namespace A;
   using namespace B;

   ref class Test {
      static void F() {
         MyClass^ m1 = gcnew MyClass();   // OK
         MyClass2<int>^ m2 = gcnew MyClass2<int>();   // OK
      }
   };
}

Kelas dasar dan antarmuka dasar tidak dapat berupa parameter jenis. Namun, kelas dasar dapat melibatkan parameter jenis sebagai argumen, seperti dalam kasus berikut:

// generic_classes_4.cpp
// compile with: /clr /c
generic <typename ItemType>
interface class IInterface {};

generic <typename ItemType>
ref class MyClass : IInterface<ItemType> {};

Konstruktor dan destruktor dijalankan sekali untuk setiap instans objek (seperti biasa); konstruktor statis dijalankan sekali untuk setiap jenis yang dibangun.

Bidang dalam Kelas Generik

Bagian ini menunjukkan penggunaan instans dan bidang statis di kelas generik.

Variabel Instans

Variabel instans dari kelas generik dapat memiliki jenis dan penginisialisasi variabel yang menyertakan parameter jenis apa pun dari kelas penutup.

Contoh: Kelas generik yang berbeda

Dalam contoh berikut, tiga instans berbeda dari kelas generik, MyClass<ItemType>, dibuat dengan menggunakan argumen jenis yang sesuai (int, double, dan string).

// generics_instance_fields1.cpp
// compile with: /clr
// Instance fields on generic classes
using namespace System;

generic <typename ItemType>
ref class MyClass {
// Field of the type ItemType:
public :
   ItemType field1;
   // Constructor using a parameter of the type ItemType:
   MyClass(ItemType p) {
   field1 = p;
   }
};

int main() {
   // Instantiate an instance with an integer field:
   MyClass<int>^ myObj1 = gcnew MyClass<int>(123);
   Console::WriteLine("Integer field = {0}", myObj1->field1);

   // Instantiate an instance with a double field:
   MyClass<double>^ myObj2 = gcnew MyClass<double>(1.23);
   Console::WriteLine("Double field = {0}", myObj2->field1);

   // Instantiate an instance with a String field:
   MyClass<String^>^ myObj3 = gcnew MyClass<String^>("ABC");
   Console::WriteLine("String field = {0}", myObj3->field1);
   }
Integer field = 123
Double field = 1.23
String field = ABC

Variabel Statis

Pada pembuatan jenis generik baru, instans baru dari variabel statis apa pun dibuat dan konstruktor statis apa pun untuk jenis tersebut dijalankan.

Variabel statis dapat menggunakan parameter jenis apa pun dari kelas penutup.

Contoh: Menggunakan variabel statis

Contoh berikut menunjukkan penggunaan bidang statis dan konstruktor statis dalam kelas generik.

// generics_static2.cpp
// compile with: /clr
using namespace System;

interface class ILog {
   void Write(String^ s);
};

ref class DateTimeLog : ILog {
public:
   virtual void Write(String^ s) {
      Console::WriteLine( "{0}\t{1}", DateTime::Now, s);
   }
};

ref class PlainLog : ILog {
public:
   virtual void Write(String^ s) { Console::WriteLine(s); }
};

generic <typename LogType>
where LogType : ILog
ref class G {
   static LogType s_log;

public:
   G(){}
   void SetLog(LogType log) { s_log = log; }
   void F() { s_log->Write("Test1"); }
   static G() { Console::WriteLine("Static constructor called."); }
};

int main() {
   G<PlainLog^>^ g1 = gcnew G<PlainLog^>();
   g1->SetLog(gcnew PlainLog());
   g1->F();

   G<DateTimeLog^>^ g2 = gcnew G<DateTimeLog^>();
   g2->SetLog(gcnew DateTimeLog());

   // prints date
   // g2->F();
}
Static constructor called.
Static constructor called.
Static constructor called.
Test1

Metode dalam Kelas Generik

Metode dalam kelas generik dapat bersifat generik sendiri; metode non-generik akan secara implisit diparameterkan oleh parameter jenis kelas.

Aturan khusus berikut berlaku untuk metode dalam kelas generik:

  • Metode dalam kelas generik dapat menggunakan parameter jenis sebagai parameter, jenis pengembalian, atau variabel lokal.

  • Metode dalam kelas generik dapat menggunakan jenis yang dibangun terbuka atau tertutup sebagai parameter, jenis pengembalian, atau variabel lokal.

Metode Non-Generik dalam Kelas Generik

Metode dalam kelas generik yang tidak memiliki parameter jenis tambahan biasanya disebut sebagai non-generik meskipun secara implisit diparameterkan oleh kelas generik penutup.

Tanda tangan metode non-generik dapat mencakup satu atau beberapa parameter jenis kelas penutup, baik secara langsung atau dalam jenis konstruksi terbuka. Contohnya:

void MyMethod(MyClass<ItemType> x) {}

Isi metode tersebut juga dapat menggunakan parameter jenis ini.

Contoh: Mendeklarasikan metode non-generik

Contoh berikut mendeklarasikan metode non-generik, ProtectData, di dalam kelas generik, MyClass<ItemType>. Metode ini menggunakan parameter ItemType jenis kelas dalam tanda tangannya dalam jenis yang dibangun terbuka.

// generics_non_generic_methods1.cpp
// compile with: /clr
// Non-generic methods within a generic class.
using namespace System;

generic <typename ItemType>
ref class MyClass {
public:
   String^ name;
   ItemType data;

   MyClass(ItemType x) {
      data = x;
   }

   // Non-generic method using the type parameter:
   virtual void ProtectData(MyClass<ItemType>^ x) {
      data = x->data;
   }
};

// ItemType defined as String^
ref class MyMainClass: MyClass<String^> {
public:
   // Passing "123.00" to the constructor:
   MyMainClass(): MyClass<String^>("123.00") {
      name = "Jeff Smith";
   }

   virtual void ProtectData(MyClass<String^>^ x) override {
      x->data = String::Format("${0}**", x->data);
   }

   static void Main() {
      MyMainClass^ x1 = gcnew MyMainClass();

      x1->ProtectData(x1);
      Console::WriteLine("Name: {0}", x1->name);
      Console::WriteLine("Amount: {0}", x1->data);
   }
};

int main() {
   MyMainClass::Main();
}
Name: Jeff Smith
Amount: $123.00**

Metode Generik dalam Kelas Generik

Anda dapat mendeklarasikan metode generik di kelas generik dan non-generik. Contohnya:

Contoh: Mendeklarasikan metode generik dan non-generik

// generics_method2.cpp
// compile with: /clr /c
generic <typename Type1>
ref class G {
public:
   // Generic method having a type parameter
   // from the class, Type1, and its own type
   // parameter, Type2
   generic <typename Type2>
   void Method1(Type1 t1, Type2 t2) { F(t1, t2); }

   // Non-generic method:
   // Can use the class type param, Type1, but not Type2.
   void Method2(Type1 t1) { F(t1, t1); }

   void F(Object^ o1, Object^ o2) {}
};

Metode non-generik masih umum dalam arti bahwa metode tersebut diparameterkan oleh parameter jenis kelas, tetapi tidak memiliki parameter jenis tambahan.

Semua jenis metode dalam kelas generik dapat bersifat generik, termasuk metode statis, instans, dan virtual.

Contoh: Mendeklarasikan dan menggunakan metode generik

Contoh berikut menunjukkan mendeklarasikan dan menggunakan metode generik dalam kelas generik:

// generics_generic_method2.cpp
// compile with: /clr
using namespace System;
generic <class ItemType>
ref class MyClass {
public:
   // Declare a generic method member.
   generic <class Type1>
   String^ MyMethod(ItemType item, Type1 t) {
      return String::Concat(item->ToString(), t->ToString());
   }
};

int main() {
   // Create instances using different types.
   MyClass<int>^ myObj1 = gcnew MyClass<int>();
   MyClass<String^>^ myObj2 = gcnew MyClass<String^>();
   MyClass<String^>^ myObj3 = gcnew MyClass<String^>();

   // Calling MyMethod using two integers.
   Console::WriteLine("MyMethod returned: {0}",
            myObj1->MyMethod<int>(1, 2));

   // Calling MyMethod using an integer and a string.
   Console::WriteLine("MyMethod returned: {0}",
            myObj2->MyMethod<int>("Hello #", 1));

   // Calling MyMethod using two strings.
   Console::WriteLine("MyMethod returned: {0}",
       myObj3->MyMethod<String^>("Hello ", "World!"));

   // generic methods can be called without specifying type arguments
   myObj1->MyMethod<int>(1, 2);
   myObj2->MyMethod<int>("Hello #", 1);
   myObj3->MyMethod<String^>("Hello ", "World!");
}
MyMethod returned: 12
MyMethod returned: Hello #1
MyMethod returned: Hello World!

Menggunakan Jenis Berlapis di Kelas Generik

Sama seperti kelas biasa, Anda dapat mendeklarasikan jenis lain di dalam kelas generik. Deklarasi kelas berlapis secara implisit diparameterkan oleh parameter jenis deklarasi kelas luar. Dengan demikian, kelas berlapis yang berbeda didefinisikan untuk setiap jenis luar yang dibangun. Misalnya, dalam deklarasi,

// generic_classes_5.cpp
// compile with: /clr /c
generic <typename ItemType>
ref struct Outer {
   ref class Inner {};
};

Jenisnya Outer<int>::Inner tidak sama dengan jenis Outer<double>::Inner.

Seperti halnya metode generik dalam kelas generik, parameter jenis tambahan dapat didefinisikan untuk jenis berlapis. Jika Anda menggunakan nama parameter jenis yang sama di kelas dalam dan luar, parameter jenis dalam akan menyembunyikan parameter jenis luar.

// generic_classes_6.cpp
// compile with: /clr /c
generic <typename ItemType>
ref class Outer {
   ItemType outer_item;   // refers to outer ItemType

   generic <typename ItemType>
   ref class Inner {
      ItemType inner_item;   // refers to Inner ItemType
   };
};

Karena tidak ada cara untuk merujuk ke parameter jenis luar, pengkompilasi akan menghasilkan peringatan dalam situasi ini.

Ketika jenis generik berlapis yang dibangun diberi nama, parameter jenis untuk jenis luar tidak disertakan dalam daftar parameter jenis untuk jenis dalam, meskipun jenis dalam secara implisit diparameterkan oleh parameter jenis luar. Dalam kasus di atas, nama jenis yang dibangun adalah Outer<int>::Inner<string>.

Contoh: Membuat dan membaca daftar tertaut

Contoh berikut menunjukkan membangun dan membaca daftar tertaut menggunakan jenis berlapis di kelas generik.

// generics_linked_list.cpp
// compile with: /clr
using namespace System;
generic <class ItemType>
ref class LinkedList {
// The node class:
public:
   ref class Node {
   // The link field:
   public:
      Node^ next;
      // The data field:
      ItemType item;
   } ^first, ^current;
};

ref class ListBuilder {
public:
   void BuildIt(LinkedList<double>^ list) {
      /* Build the list */
      double m[5] = {0.1, 0.2, 0.3, 0.4, 0.5};
      Console::WriteLine("Building the list:");

      for (int n=0; n<=4; n++) {
         // Create a new node:
         list->current = gcnew LinkedList<double>::Node();

         // Assign a value to the data field:
         list->current->item = m[n];

         // Set the link field "next" to be the same as
         // the "first" field:
         list->current->next = list->first;

         // Redirect "first" to the new node:
         list->first = list->current;

         // Display node's data as it builds:
         Console::WriteLine(list->current->item);
      }
   }

   void ReadIt(LinkedList<double>^ list) {
      // Read the list
      // Make "first" the "current" link field:
      list->current = list->first;
      Console::WriteLine("Reading nodes:");

      // Read nodes until current == null:
      while (list->current != nullptr) {
         // Display the node's data field:
         Console::WriteLine(list->current->item);

         // Move to the next node:
         list->current = list->current->next;
      }
   }
};

int main() {
   // Create a list:
   LinkedList<double>^ aList = gcnew LinkedList<double>();

   // Initialize first node:
   aList->first = nullptr;

   // Instantiate the class, build, and read the list:
   ListBuilder^ myListBuilder = gcnew ListBuilder();
   myListBuilder->BuildIt(aList);
   myListBuilder->ReadIt(aList);
}
Building the list:
0.1
0.2
0.3
0.4
0.5
Reading nodes:
0.5
0.4
0.3
0.2
0.1

Properti, Peristiwa, Pengindeks, dan Operator di Kelas Generik

  • Properti, peristiwa, pengindeks, dan operator dapat menggunakan parameter jenis kelas generik yang menyertakan sebagai nilai pengembalian, parameter, atau variabel lokal, seperti kapan ItemType adalah parameter jenis kelas:

    public ItemType MyProperty {}
    
  • Properti, peristiwa, pengindeks, dan operator tidak dapat diparameterkan sendiri.

Contoh: Mendeklarasikan properti instans

Contoh ini menunjukkan deklarasi properti instans dalam kelas generik.

// generics_generic_properties1.cpp
// compile with: /clr
using namespace System;

generic <typename ItemType>
ref class MyClass {
private:
   property ItemType myField;

public:
   property ItemType MyProperty {
      ItemType get() {
         return myField;
      }
      void set(ItemType value) {
         myField = value;
      }
   }
};

int main() {
   MyClass<String^>^ c = gcnew MyClass<String^>();
   MyClass<int>^ c1 = gcnew MyClass<int>();

   c->MyProperty = "John";
   c1->MyProperty = 234;

   Console::Write("{0}, {1}", c->MyProperty, c1->MyProperty);
}
John, 234

Contoh: Kelas generik dengan peristiwa

Contoh berikutnya menunjukkan kelas generik dengan peristiwa.

// generics_generic_with_event.cpp
// compile with: /clr
// Declare a generic class with an event and
// invoke events.
using namespace System;

// declare delegates
generic <typename ItemType>
delegate void ClickEventHandler(ItemType);

// generic class that defines events
generic <typename ItemType>
ref class EventSource {
public:
   // declare the event OnClick
   event ClickEventHandler<ItemType>^ OnClick;
   void FireEvents(ItemType item) {
      // raises events
      OnClick(item);
   }
};

// generic class that defines methods that will called when
// event occurs
generic <typename ItemType>
ref class EventReceiver {
public:
   void OnMyClick(ItemType item) {
   Console::WriteLine("OnClick: {0}", item);
   }
};

int main() {
   EventSource<String^>^ MyEventSourceString =
                   gcnew EventSource<String^>();
   EventSource<int>^ MyEventSourceInt = gcnew EventSource<int>();
   EventReceiver<String^>^ MyEventReceiverString =
                   gcnew EventReceiver<String^>();
   EventReceiver<int>^ MyEventReceiverInt = gcnew EventReceiver<int>();

   // hook handler to event
   MyEventSourceString->OnClick += gcnew ClickEventHandler<String^>(
       MyEventReceiverString, &EventReceiver<String^>::OnMyClick);
   MyEventSourceInt->OnClick += gcnew ClickEventHandler<int>(
             MyEventReceiverInt, &EventReceiver<int>::OnMyClick);

   // invoke events
   MyEventSourceString->FireEvents("Hello");
   MyEventSourceInt->FireEvents(112);

   // unhook handler to event
   MyEventSourceString->OnClick -= gcnew ClickEventHandler<String^>(
        MyEventReceiverString, &EventReceiver<String^>::OnMyClick);
   MyEventSourceInt->OnClick -= gcnew ClickEventHandler<int>(
        MyEventReceiverInt, &EventReceiver<int>::OnMyClick);
}

Struktur Generik

Aturan untuk mendeklarasikan dan menggunakan struktur generik sama dengan aturan untuk kelas generik, kecuali untuk perbedaan yang dicatat dalam referensi bahasa Visual C++.

Contoh: Mendeklarasikan struktur generik

Contoh berikut mendeklarasikan struktur generik, MyGenStruct, dengan satu bidang, myField, dan menetapkan nilai dari berbagai jenis (int, double, String^) ke bidang ini.

// generics_generic_struct1.cpp
// compile with: /clr
using namespace System;

generic <typename ItemType>
ref struct MyGenStruct {
public:
   ItemType myField;

   ItemType AssignValue(ItemType item) {
      myField = item;
      return myField;
   }
};

int main() {
   int myInt = 123;
   MyGenStruct<int>^ myIntObj = gcnew MyGenStruct<int>();
   myIntObj->AssignValue(myInt);
   Console::WriteLine("The field is assigned the integer value: {0}",
            myIntObj->myField);

   double myDouble = 0.123;
   MyGenStruct<double>^ myDoubleObj = gcnew MyGenStruct<double>();
   myDoubleObj->AssignValue(myDouble);
   Console::WriteLine("The field is assigned the double value: {0}",
            myDoubleObj->myField);

   String^ myString = "Hello Generics!";
   MyGenStruct<String^>^ myStringObj = gcnew MyGenStruct<String^>();
   myStringObj->AssignValue(myString);
   Console::WriteLine("The field is assigned the string: {0}",
            myStringObj->myField);
}
The field is assigned the integer value: 123
The field is assigned the double value: 0.123
The field is assigned the string: Hello Generics!

Baca juga

Generik