Универсальные классы (C++/CLI)

Универсальный класс объявляется в следующей форме:

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

Заметки

В приведенном синтаксисе используются следующие элементы:

  • attributes (необязательный)
    Дополнительные описательные данные. Дополнительные сведения об атрибутах и классах атрибутов см. в Атрибутах.

  • class-key
    class или typename

  • type-parameter-identifier(s),
    Разделенный запятыми список идентификаторов, задающих имена параметров типа.

  • constraint-clauses
    Список (неразделенный запятыми), где предложения определяют ограничения параметров типа. Принимает вид:

    where type-parameter-identifier : constraint-list ...

  • constraint-list
    class-or-interface[, ...]

  • accessibility-modifiers
    Модификаторы доступности для универсального класса. Для Среда выполнения Windows единственный разрешенный модификатор - это private. Для среды CLR разрешенными модификаторами являются private и public.

  • identifier
    Имя универсального класса, любой допустимый идентификатор C++.

  • modifiers (необязательный)
    Допустимые модификаторы включают в себя sealed и abstract.

  • base-list
    Список, содержащий один базовый класс и любые реализованные интерфейсы, разделенные запятыми.

  • class-body
    Тело класса, содержащее поля, функции-члены и т. д.

  • declarators
    Объявления любых переменных данного типа. Например:идентификатор ^[, …]

Можно объявить универсальные классы, например такие (обратите внимание, что ключевое слово класс может использоваться вместо имени типа). В этом примере, ItemType, KeyType и ValueType — неизвестные типы, определенные в типах. HashTable<int, int> — сконструированный тип универсального типа HashTable<KeyType, ValueType>. Несколько различных сконструированных типов можно построить из одного универсального типа. Сконструированные типы, сконструированные из универсальных классов обрабатываются так же, как и любой другой тип ссылочного класса.

// 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>();
}

И типы значений (или встроенные типы, такие как int или double или определяемые пользователем типы значений), и ссылочные типы могут использоваться в качестве аргумента универсального типа. Синтаксис внутри универсального определения одинаково независим. Синтаксически, неизвестный тип обрабатывается, как если бы он был ссылочным типом. Однако среда выполнения может определить, что фактически используемый тип является типом значения, и заменить соответствующий созданный код для прямого доступа к членам. Типы значений, используемые в качестве аргументов универсального типа, не упаковываются и поэтому не страдают от снижения производительности, связанного с упаковкой. Синтаксис, используемый в теле универсального шаблона, должен быть T^ и '->' вместо '.' Любое использование ref new, gcnew (расширения компонентов C++) для параметра типа будет соответствующе обработано средой выполнения как простое создание типа значения, если аргумент типа является типом значения.

Можно также объявить универсальный класс с Ограничения, применяемые к параметрам универсальных типов (C++/CLI) для типов, которые можно использовать как параметр типа. В следующем примере любой тип, используемый для ItemType должен реализовать интерфейс IItem. Попытка использовать int, например, который не реализует IItem, приведет к ошибке компиляции, поскольку аргумент типа не удовлетворяет ограничению.

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

Универсальные классы в одном пространстве имен не могут быть перегружены только изменением числа или типов параметров типа. Однако, если каждый класс живет в различном пространстве имен, они могут быть перегружены. Например, рассмотрим следующие 2 класса, MyClass и MyClass<ItemType> в пространствах имен A и B. Два класса затем можно перегружать в третьем пространстве имен 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
      }
   };
}

Базовый класс и базовые интерфейсы не могут быть параметрами типа. Однако базовый класс может содержать параметр типа в качестве аргумента, как показано в следующем примере:

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

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

Конструкторы и деструкторы выполняются один раз для каждого экземпляра объекта (обычно); статические конструкторы выполняются один раз для каждого конкретного сконструированного типа.

Поля в универсальных классах

В этом разделе показано использование экземпляра и статических полей в универсальных классах.

Переменные экземпляров

Переменные экземпляра универсального класса могут иметь типы и инициализаторы переменной, в том числе любые параметры типа из включающего класса.

Пример

В следующем примере три различных экземпляра универсального класса, MyClass<ItemType>, создаются с помощью аргументов соответствующего типа (int, double и 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);
   }
  

В следующем примере демонстрируется использование статических полей и статического конструктора в универсальном классе.

// 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();
}
  

В следующем примере объявляется неуниверсальный метод, ProtectData, внутри универсального класса, MyClass<ItemType>. Метод использует параметр типа класса ItemType в сигнатуре в открытом сконструированном типе.

// 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();
}
  
// 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) {}
};

Неуниверсальный метод по-прежнему универсален в том смысле, что он параметризован параметром типа класса, но не содержит дополнительных параметров типа.

Все типы методов в универсальных классах могут быть универсальными, включая статические, экземплярные и виртуальные методы.

В следующем примере демонстрируется объявление и использование универсальных методов в универсальных классах:

// 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!");
}
  
// 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);
}
  

В этом примере показано объявление свойства экземпляра в универсальном классе.

// 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);
}
  

В следующем примере показан универсальный класс с событием.

// 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);
}

В следующем примере объявляется универсальная структура, MyGenStruct с одним полем, myField и присваиваются этому полю значения различных типов (int, double, String^).

// 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);
}
  

Статические переменные.

При создании универсального типа создаются новые экземпляры всех статических переменных и выполняется любой статический конструктор для данного типа.

Статические переменные могут использовать любые параметры типа из включающего класса.

Методы в универсальных классах

Методы в универсальных классах могут быть универсальными самостоятельно; неуниверсальные методы будут неявно параметризованы параметром типа класса.

Следующие специальные правила применяются к методам в универсальных классах:

  • Методы в универсальных классах могут использовать параметры типа в качестве параметров, возвращаемых типов или локальных переменных.

  • Методы в универсальных классах могут использовать открытые или закрытые сконструированные типы в качестве параметров, возвращаемых типов или локальных переменных.

Неуниверсальные методы в универсальных классах

Методы в универсальных классах, которые не имеют дополнительные параметры типа, обычно называются неуниверсальными, хотя они неявно параметризованы включающим универсальным классом.

Сигнатура неуниверсального метода может включать один или несколько параметров типа включающего класса, напрямую или в виде открытого сконструированного типа. Примеры.

void MyMethod(MyClass<ItemType> x) {}

Тело таких методов также может использовать эти параметры типа.

Универсальные методы в универсальных классах

Можно объявить универсальные методы в универсальных и неуниверсальных классах. Примеры.

Использование вложенных типов в универсальных классах

Так же как с обычными классами, можно объявить другие типы внутри универсального класса. Объявление вложенного класса неявно параметризовано параметрами типа объявления внешнего класса. Таким образом, указанный вложенный класс определяется для каждого сконструированного внешнего типа. Например, в объявлении

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

Тип Outer<int>::Inner отличается от типа Outer<double>::Inner.

Как и в случае с универсальными методами в универсальных классах, дополнительные параметры типа можно задавать для вложенных типов. Если используются одинаковые имена параметров типа во внутреннем и внешнем классе, внутренний параметр типа скрывает внешний параметр типа.

// 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
   };
};

Поскольку нет обращения к внешнему параметру типа, компилятор создает предупреждение в этой ситуации.

После именования сконструированных вложенных универсальных типов, параметр типа для внешнего типа не включается в список параметров типа для внутреннего типа, даже если внутренний тип неявно параметризован параметром типа внешнего типа. В приведенном выше случае имя сконструированного типа будет Outer<int>::Inner<string>.

В следующем примере демонстрируется построение и чтение связанного списка с помощью вложенных типов в универсальных классах.

Свойства, события, индексаторы и операторы в универсальных классах

  • Свойства, события, индексаторы и операторы могут использовать параметры типа включающего универсального класса в качестве возвращаемых значений, параметров или локальных переменных, например, если ItemType является параметром типа класса:

    public ItemType MyProperty {}
    
  • События, свойства, индексаторы и операторы не могут параметризованы самостоятельно.

Универсальные структуры

Правила для объявления и использования универсальных структур те же, что и для универсальных классов, за исключением различий, описанных в справочнике по языку Visual C++.

См. также

Другие ресурсы

Универсальные типы и методы (расширения компонентов C++)