Generics
Generics
Generic Functions |
Introduction |
The templates we studied in the previous lesson are part of the C++ programming language. To update their usefulness and adapt them to the .NET Framework, Microsoft added the concept of generic to the C++/CLI language. Like a template, a generic is a type of value used by a function without the function knowing what data type the value is made for. While templates in their format are not formally used by other languages of the .NET Framework such as C# or Visual Basic, generics are.
Generic Creation |
In the previous lesson, we saw that you might have a function used to display different types of values:
using namespace System; // Display the value of an integer void Show(int value) { Console::WriteLine(value); } // Display the value of a double-precesion value void Show(double value) { Console::WriteLine(value); } // Display the value of a character void Show(__wchar_t value) { Console::WriteLine(value); } int main() { // Call the version of the function that displays an integer Console::Write(L"Value: "); Show(246); // Call the version of the function that displays a character Console::Write(L"Value: "); Show('G'); // Call the version of the function that displays a decimal Console::Write(L"Value: "); Show(355.65); return 0; }
We then saw that you could create a template function with a parameter whose type was not known in advance:
template <class TypeOfValue> void Show(TypeOfValue value) { Console::WriteLine(value); }
After creating the function, you could call it by specifying the type of value it would process:
using namespace System; template <class TypeOfValue> void Show(TypeOfValue value) { Console::WriteLine(value); } int main() { // Call the version of the function that displays an integer int Integer = 246; Console::Write(L"Value: "); Show<int>(Integer); // Call the version of the function that displays a character __wchar_t Character = L'G'; Console::Write(L"Value: "); Show<__wchar_t>(Character); // Call the version of the function that displays a decimal double DoublePrecision = 355.65; Console::Write(L"Value: "); Show<double>(DoublePrecision); return 0; }
To create a generic, instead of template, you use the generic keyword following the format we reviewed for the template in the previous lesson. Here is an example:
using namespace System; generic <class TypeOfValue> void Show(TypeOfValue value) { Console::WriteLine(value); } int main() { // Call the version of the function that displays an integer int Integer = 246; Console::Write(L"Value: "); Show<int>(Integer); // Call the version of the function that displays a character __wchar_t Character = L'G'; Console::Write(L"Value: "); Show<__wchar_t>(Character); // Call the version of the function that displays a decimal double DoublePrecision = 355.65; Console::Write(L"Value: "); Show<double>(DoublePrecision); return 0; }
This would produce:
Value: 246 Value: G Value: 355.65 Press any key to continue . . .
When creating a template, we saw that you could replace the class keyword with typename. This is also valid for a generic. Here is an example:
generic <typename TypeOfValue> void Show(TypeOfValue value) { Console::WriteLine(value); }
Differences Between Templates and Generics |
As mentioned earlier, templates are part of the C++ language but generics are mainly a concept of the .NET Framework. Some concepts of templates, as they relate to C++, are not present in the CLR because other languages of the CLI don't use them. For example, we saw that a template parameter could be passed as a reference. Consider the following function:
template <class TypeOfValue> void Show(TypeOfValue &value) { Console::WriteLine(value); }
This would compile fine. Imagine you make this a generic:
generic <class TypeOfValue> void Show(TypeOfValue &value) { Console::WriteLine(value); }
In Microsoft Visual C++ 2008, you would receive a C3229 error:
error C3229: 'TypeOfValue &' : indirections on a generic type parameter are not allowed compiler using 'TypeOfValue' to continue parsing
We also saw that a template parameter could be passed as a pointer. Since pointers are not directly supported by other CLI languages like Visual Basic, a parameter cannot be passed as a pointer. Therefore, the following will not work:
generic <class TypeOfValue> void Show(TypeOfValue *value) { Console::WriteLine(*value); }
Like a template, the parameter type of a generic can be regular value of a primitive type. Unlike a template, if the parameter type of a generic is a primitive type, it cannot be passed as a handle. For this reason, the following would not compile:
using namespace System; generic <class TypeOfValue> void Show(TypeOfValue ^ value) { Console::WriteLine(value); } int main() { // Call the function to display the number of pages of a book int ^ a = gcnew int(704); Console::Write(L"Number of Pages: "); Show<int>(a); // Call the function to display the character gender __wchar_t ^ u = gcnew __wchar_t(L'M'); Console::Write(L"Employee Gender: "); Show<__wchar_t>(u); // Call the function to display somebody's hourly salary double ^ x = gcnew double(18.48); Console::Write(L"Hourly Salary: "); Show<double>(x); return 0; }
In Microsoft Visual C++ 2008, the program would produce the following errors:
error C3229: 'TypeOfValue ^' : indirections on a generic type parameter are not allowed compiler using 'TypeOfValue' to continue parsing error C2664: 'Show' : cannot convert parameter 1 from 'System::Int32 ^' to 'int' No user-defined-conversion operator available, or There is no context in which this conversion is possible error C2664: 'Show' : cannot convert parameter 1 from 'System::Char ^' to 'wchar_t' No user-defined-conversion operator available, or There is no context in which this conversion is possible error C2664: 'Show' : cannot convert parameter 1 from 'System::Double ^' to 'double' No user-defined-conversion operator available, or There is no context in which this conversion is possible
But, you can declare a handle and pass it as the value of a pointer when calling the generic function. Here are examples:
using namespace System; generic <class TypeOfValue> void Show(TypeOfValue value) { Console::WriteLine(value); } int main() { // Call the function to display the number of pages of a book int ^ a = gcnew int(704); Console::Write(L"Number of Pages: "); Show<int>(*a); // Call the function to display the character gender __wchar_t ^ u = gcnew __wchar_t(L'M'); Console::Write(L"Employee Gender: "); Show<__wchar_t>(*u); // Call the function to display somebody's hourly salary double ^ x = gcnew double(18.48); Console::Write(L"Hourly Salary: "); Show<double>(*x); return 0; }
Generics Methods |
Introduction |
We saw that you could create a function as a generic. When it comes to a class, one of the most fundamental ways you can involve a generic is to implement the method of a class as a generic. You can do this by preceding the definition of the method with the declaration of a generic, exactly as we did in the previous sections. Here is an example:
public ref class General { public: generic <class T> void Show() { } };
Passing a Parameter Type |
If you plan to process a value of the parameter type in the method, you can pass an argument to the method. Here is an example:
public ref class General { public: generic <class T> void Show(T value) { } };
In the body of the method, you can use the argument as you see fit. As we saw in the previous lesson and in the previous sections, at a minimum, you can display the value of the argument by passing it to the Console::WriteLine() method. Before calling the method, you can first declare a variable or a handle of the class. You can then call the method using the period or the arrow operator. Here are examples:
using namespace System; public ref class General { public: generic <class T> void Show(T value) { Console::WriteLine(value); } }; int main() { // Call the version of the function that displays an integer int Integer = 246; General gen; Console::Write(L"Value: "); gen.Show<int>(Integer); // Call the version of the function that displays a character __wchar_t Character = L'G'; Console::Write(L"Value: "); gen.Show<__wchar_t>(Character); // Call the version of the function that displays a decimal double DoublePrecision = 355.65; Console::Write(L"Value: "); gen.Show<double>(DoublePrecision); return 0; }
This would produce:
Value: 246 Value: G Value: 355.65 Press any key to continue . . .
If you want to implement the method outside of its class, make sure you precede it with the generic declaration. Here is an example:
using namespace System; public ref class General { public: generic <class T> void Show(T value); }; generic <class T> void General::Show(T value) { Console::WriteLine(value); } int main() { return 0; }
Remember that you can also declare a class on the managed heap using the gcnew operator and access its member(s) using the -> operator:
using namespace System; public ref class General { public: generic <class T> void Show(T value); }; generic <class T> void General::Show(T value) { Console::WriteLine(value); } int main() { // Call the version of the function that displays an integer int Integer = 246; General ^ gen = gcnew General; Console::Write(L"Value: "); gen->Show<int>(Integer); // Call the version of the function that displays a character __wchar_t Character = L'G'; Console::Write(L"Value: "); gen->Show<__wchar_t>(Character); // Call the version of the function that displays a decimal double DoublePrecision = 355.65; Console::Write(L"Value: "); gen->Show<double>(DoublePrecision); return 0; }
A generic method can also be declared as static, in which case you would access it using the :: operator. Here is an example:
using namespace System; public ref class General { public: generic <class T> static void Show(T value) { Console::WriteLine(value); } }; int main() { // Call the version of the function that displays an integer Console::Write(L"Value: "); General::Show<int>(246); // Call the version of the function that displays a character Console::Write(L"Value: "); General::Show<__wchar_t>(L'G'); // Call the version of the function that displays a decimal Console::Write(L"Value: "); General::Show<double>(355.65); return 0; }
Returning a Parameter Type |
After setting a generic declaration before a method, just as you can pass it as argument, you may want the method to return a value of the parameter type. To do this, simply specify the return type. Here is an example:
public ref class General { public: generic <class T> void Show(T value); generic <class T> T GetValue(); };
When implementing the method, make sure you return the parameter type before the method exits. Here is an example:
generic <class T> T General::GetValue() { T val; return val; }
Fundamentals of Generics With Classes |
Introduction |
Instead of specifying that an individual method is generic, you can make the whole class generic. This allows you to specify a parameter type that would be available throughout the class to the other members. To create a generic class, precede its declaration with the generic <class TypeName> or generic <typename TypeName> declaration. Here is an example:
generic <class T> public ref class General { };
In the body of the class, you can declare members of the parameter type. Here is an example:
generic <class T> public ref class General { private: T t; };
You can pass the type as argument to a method or you can create a method that returns the parameter type. As mentioned for a template, if you want to define a method in the body of its class, you can simply use the parameter type as you see fit. Here are examples:
generic <class T> public ref class General { private: T t; public: void Show(T value) { Console::WriteLine(value); } T GetValue() { return t; } };
If you prefer to implement the method globally, first precede it with a generic <class TypeName> or generic <typename TypeName> declaration. Then, before the :: operator that specifies the class, include the parameter type in <>. Here is an example:
generic <class T> public ref class General { private: T t; public: void Show(T value) { Console::WriteLine(value); } T GetValue() { return t; } void SetValue(T value); }; generic <class T> void General<T>::SetValue(T value) { t = value; }
Again, as done for a template, when declaring a handle for the class, you must specify the type of value it would process. Here are three examples:
using namespace System; generic <class T> public ref class General { private: T t; public: void Show(T value) { Console::WriteLine(value); } T GetValue() { return t; } void SetValue(T value); }; generic <class T> void General<T>::SetValue(T value) { t = value; } int main() { // Call the version of the function that displays an integer General<int> ^ IntType = gcnew General<int>; IntType->SetValue(246); Console::Write(L"Value: "); IntType->Show(IntType->GetValue()); // Call the version of the function that displays a character General<__wchar_t> ^ CharType = gcnew General<__wchar_t>; CharType->SetValue(L'G'); Console::Write(L"Value: "); CharType->Show(CharType->GetValue()); // Call the version of the function that displays a decimal General<double> ^ DoubleType = gcnew General<double>; DoubleType->SetValue(355.65); Console::Write(L"Value: "); DoubleType->Show(DoubleType->GetValue()); return 0; }
In the preceding example, we passed the values directly to the Set method. If you want, you can first declare the variable, appropriately initialize it, and then pass it to the method. You can also create a handle and initialize it, before passing it to the method as a pointer. Here are examples:
using namespace System; generic <class T> public ref class General { private: T t; public: void Show(T value) { Console::WriteLine(value); } T GetValue() { return t; } void SetValue(T value); }; generic <class T> void General<T>::SetValue(T value) { t = value; } int main() { int ^ IntValue = gcnew int(246); General<int> ^ IntType = gcnew General<int>; IntType->SetValue(*IntValue); // Call the version of the method that displays an integer Console::Write(L"Value: "); IntType->Show(IntType->GetValue()); __wchar_t ^ CharValue = gcnew __wchar_t(L'G'); General<__wchar_t> ^ CharType = gcnew General<__wchar_t>; CharType->SetValue(*CharValue); // Call the version of the method that displays a character Console::Write(L"Value: "); CharType->Show(CharType->GetValue()); double ^ DoubleValue = gcnew double(355.65); General<double> ^ DoubleType = gcnew General<double>; DoubleType->SetValue(*DoubleValue); // Call the version of the method that displays a decimal Console::Write(L"Value: "); DoubleType->Show(DoubleType->GetValue()); return 0; }
A Generic Class With Multiple Types |
If you want the class to process many values and you cannot determine the precise types of some of those values at the time you are creating the class, you can specify more than one parameter type for the generic class. To do this, before the class creation, write the generic<> declaration. Inside the <> operator, specify the class TypeName or typename TypeName combinations separated by commas. Here is an example of a generic class with two parameter types:
generic <class T, class V> public ref class General { };
If you know for sure that the parameters will be of the same type, you can use one method to process both. Otherwise, you can declare the necessary members for each type. You can also create a method that would take many arguments with each argument of a particular type. Here are examples:
generic <class T, class V> public ref class General { private: T t; V v; public: void SetTValue(T value); T GetTValue(); void SetVValue(V value); V GetVValue(); void Show(T tValue, V vValue); };
If you decide to implement a method in the body of the class, you can use or ignore the parameter type as you see fit. If you want to implement a method outside of its class, first precede it with the same declaration made before the class. Second, instead of a single parameter inside of <>, specify the appropriate number. Inside of each method, you can then manipulate or ignore the parameter(s). Here are examples:
using namespace System; generic <class T, class V> public ref class General { private: T t; V v; public: void SetTValue(T value); T GetTValue(); void SetVValue(V value); V GetVValue(); void Show(T tValue, V vValue); }; generic <class T, class V> void General<T, V>::SetTValue(T value) { t = value; } generic <class T, class V> T General<T, V>::GetTValue() { return t; } generic <class T, class V> void General<T, V>::SetVValue(V value) { v = value; } generic <class T, class V> V General<T, V>::GetVValue() { return v; } generic <class T, class V> void General<T, V>::Show(T tValue, V vValue) { Console::WriteLine(L"{0} + {1} = {2}", tValue, vValue, tValue + vValue); }
When declaring a variable for the class, make sure you appropriately specify the list of parameter types. Here are two examples:
int main() { General<int, int> ^ IntTypes = gcnew General<int, int>; IntTypes->SetTValue(246); IntTypes->SetVValue(6088); IntTypes->Show(IntTypes->GetTValue(), IntTypes->GetVValue()); General<double, double> ^ DoubleTypes = gcnew General<double, double>; DoubleTypes->SetTValue(355.65); DoubleTypes->SetVValue(1785.426); DoubleTypes->Show(DoubleTypes->GetTValue(), DoubleTypes->GetVValue()); return 0; }
This would produce:
246 + 6088 = 6334 355.65 + 1785.426 = 2141.076 Press any key to continue . . .
If a generic class has more than one parameter type, they don't have to be of the same type. At the time you are creating the class, you may not specify their types but you can anticipate that they would be different. It is when you declare the variable that you would need to determine their precise types. Here are examples:
using namespace System; generic <class T, class V> public ref class General { private: T t; V v; public: void SetTValue(T value); T GetTValue(); void SetVValue(V value); V GetVValue(); void Show(T tValue, V vValue); }; generic <class T, class V> void General<T, V>::SetTValue(T value) { t = value; } generic <class T, class V> T General<T, V>::GetTValue() { return t; } generic <class T, class V> void General<T, V>::SetVValue(V value) { v = value; } generic <class T, class V> V General<T, V>::GetVValue() { return v; } generic <class T, class V> void General<T, V>::Show(T tValue, V vValue) { Console::WriteLine(L"First: {0}\nSecond: {1}", tValue, vValue); } int main() { General<int, __wchar_t> ^ Set1 = gcnew General<int, __wchar_t>; Set1->SetTValue(246); Set1->SetVValue(L'F'); Set1->Show(Set1->GetTValue(), Set1->GetVValue()); Console::WriteLine(); General<__wchar_t, double> ^ Set2 = gcnew General<__wchar_t, double>; Set2->SetTValue(L'$'); Set2->SetVValue(1785.426); Set2->Show(Set2->GetTValue(), Set2->GetVValue()); Console::WriteLine(); General<Byte, double> ^ Set3 = gcnew General<Byte, double>; Set3->SetTValue(55); Set3->SetVValue(47397.04); Set3->Show(Set3->GetTValue(), Set3->GetVValue()); Console::WriteLine(); return 0; }
This would produce:
First: 246 Second: F First: $ Second: 1785.426 First: 55 Second: 47397.04 Press any key to continue . . .
Even if the parameters are of primitive types, you can first declare the variables and pass them to the class. You can also create a handle for each type before passing them to the class.
Using a Class as a Parameter Type |
As you should know by now, if you want to use a .NET Framework built-in class, you must use it as a handle. If you create a generic class and you want to process a managed object in it, you must treat the object as a handle. To use such a class, when declaring a variable for your generic class, make sure you specify the ^ operator for the parameter type, inside the <> operator. Here is an example:
using namespace System; generic <class T> public ref class General { private: T t; public: void SetValue(T value); T GetValue(); void Show(T Value); }; generic <class T> void General<T>::SetValue(T value) { t = value; } generic <class T> T General<T>::GetValue() { return t; } generic <class T> void General<T>::Show(T Value) { Console::WriteLine(L"{0}", Value); } int main() { General<int> ^ IntType = gcnew General<int>; IntType->SetValue(246); IntType->Show(IntType->GetValue()); General<String ^> ^ strType = gcnew General<String ^>; strType->SetValue(L"Australia"); strType->Show(strType->GetValue()); General<double> ^ DoubleType = gcnew General<double>; DoubleType->SetValue(355.65); DoubleType->Show(DoubleType->GetValue()); return 0; }
This would produce:
246 Australia 355.65 Press any key to continue . . .
If the class was meant to process more than one value, you must include each with its own ^ operator. Here is an example:
using namespace System; generic <class T, class V> public ref class General { private: T t; V v; public: void SetTValue(T value); T GetTValue(); void SetVValue(V value); V GetVValue(); void Show(T tValue, V vValue); }; generic <class T, class V> void General<T, V>::SetTValue(T value) { t = value; } generic <class T, class V> T General<T, V>::GetTValue() { return t; } generic <class T, class V> void General<T, V>::SetVValue(V value) { v = value; } generic <class T, class V> V General<T, V>::GetVValue() { return v; } generic <class T, class V> void General<T, V>::Show(T tValue, V vValue) { Console::WriteLine(L"{0} {1}", tValue, vValue); } int main() { General<int, int> ^ IntTypes = gcnew General<int, int>; IntTypes->SetTValue(246); IntTypes->SetVValue(6088); IntTypes->Show(IntTypes->GetTValue(), IntTypes->GetVValue()); General<String ^, String ^> ^ strTypes = gcnew General<String ^, String ^>; strTypes->SetTValue(L"United"); strTypes->SetVValue(L"Stations"); strTypes->Show(strTypes->GetTValue(), strTypes->GetVValue()); General<double, double> ^ DoubleTypes = gcnew General<double, double>; DoubleTypes->SetTValue(355.65); DoubleTypes->SetVValue(1785.426); DoubleTypes->Show(DoubleTypes->GetTValue(), DoubleTypes->GetVValue()); return 0; }
This would produce:
246 6088 United Stations 355.65 1785.426 Press any key to continue . . .
Generics and Inheritance |
Introduction |
Consider the following geometric figures:
Square | Rectangle | Trapezoid | Parallelogram |
Notice that these are geometric figures with each having four sides. From what we learned in Lesson 21, we can create a base class to prepare it for inheritance. If the class is very general, we can make it a generic one. We can set a data type as an unknown type, anticipating that the dimensions of the figure can be considered as integer or double-precision types. Here is an example:
Header File: Quadrilateral.h |
#pragma once using namespace System; generic <typename T> public ref class CQuadrilateral { protected: T _base; T _height; String ^ _name; public: virtual property T Base { T get() { return _base; } void set(T b) { _base = b; } } virtual property T Height { T get() { return _height; } void set(T h) { _height = h; } } virtual property String ^ Name { String ^ get() { return _name; } void set(String ^ value) { _name = value; } } public: CQuadrilateral(); CQuadrilateral(String ^ name); CQuadrilateral(T base, T height); CQuadrilateral(String ^ name, T base, T height); virtual String ^ Describe(); virtual void ShowCharacteristics(); }; |
Source File: Quadrilateral.cpp |
#include "Quadrilateral.h" generic <typename T> CQuadrilateral<T>::CQuadrilateral() { _name = L"Quadrilateral"; } generic <typename T> CQuadrilateral<T>::CQuadrilateral(String ^ name) { _name = L"Quadrilateral"; } generic <typename T> CQuadrilateral<T>::CQuadrilateral(T base, T height) : _name(L"Quadrilateral"), _base(base), _height(height) { } generic <typename T> CQuadrilateral<T>::CQuadrilateral(String ^ name, T base, T height) : _name(name), _base(base), _height(height) { } generic <typename T> String ^ CQuadrilateral<T>::Describe() { return L"A quadrilateral is a geometric figure with four sides"; } generic <typename T> void CQuadrilateral<T>::ShowCharacteristics() { Console::WriteLine(L"Geometric Figure: {0}", this->Name); Console::WriteLine(L"Description: {0}", this->Describe()); Console::WriteLine(L"Base: {0}", this->Base); Console::WriteLine(L"Height: {0}", this->Height); } |
Source File: Exercise.cpp |
#include "Quadrilateral.h" using namespace System; int main() { // Trapezoid with equal sides CQuadrilateral<double> ^ Kite = gcnew CQuadrilateral<double>(L"Beach Kite", 18.64, 18.64); Kite->ShowCharacteristics(); Console::WriteLine(); // Rectagle, in meters CQuadrilateral<Byte> ^ BasketballStadium = gcnew CQuadrilateral<Byte>; BasketballStadium->Name = L"Basketball Stadium"; BasketballStadium->Base = 15; BasketballStadium->Height = 28; BasketballStadium->ShowCharacteristics(); Console::WriteLine(); return 0; } |
This would produce:
Geometric Figure: Beach Kite Description: A quadrilateral is a geometric figure with four sides Base: 18.64 Height: 18.64 Geometric Figure: Basketball Stadium Description: A quadrilateral is a geometric figure with four sides Base: 15 Height: 28 Press any key to continue . . .
If you have a generic class that can serve as a foundation for another class, you can derive one class from the generic one. The basic formula to use is:
Options generic <typename TypeName> AccessLevel ref/value class/struct NewClassName : public BaseClassName<TypeName> { };
You can start with some options; if you don't have any, you can ignore this factor. The generic keyword is required. Inside of <>, type either typename or class followed by a name for the parameter type. The AccessLevel is optional. This means that you can ignore it or set it to private or public. The optional AccessLevel is followed by either the ref or the value keyword, followed by the class or the struct keyword, and followed by the name of the new class. After the name of the new class, type the : operator required for inheritance, followed by the public keyword. Enter the name of the parent class followed by <> and, inside of this operator, enter the parameter type.
Here is an example of a generic class named CSquare that derives from another generic class named CQuadrilateral:
generic <typename T> public ref class CSquare : public CQuadrilateral<T> { };
In the body of the new class, you can use the parameter type as you see fit. For example, you can declare some member variables of that type. You can create methods that return the parameter type or you can pass arguments of the parameter type. Here are examples of methods:
Header File: Square.h |
generic <typename T> public ref class CSquare : public CQuadrilateral<T> { public: CSquare(); CSquare(String ^ name); CSquare(T side); CSquare(String ^ name, T side); virtual String ^ Describe() new; virtual void ShowCharacteristics() override; }; |
When implementing the methods of the new class, use the member variables of the parameter and the argument(s) based on the parameter type as you see fit. Here are examples:
Source File: Square.cpp |
#include "Square.h" generic <typename T> CSquare<T>::CSquare() { CQuadrilateral::_name = L"Square"; } generic <typename T> CSquare<T>::CSquare(String ^ name) { CQuadrilateral::_name = L"Square"; } generic <typename T> CSquare<T>::CSquare(T side) { CQuadrilateral::_name = L"Square"; CQuadrilateral::_base = side; CQuadrilateral::_height = side; } generic <typename T> CSquare<T>::CSquare(String ^ name, T side) { CQuadrilateral::_name = name; CQuadrilateral::_base = side; CQuadrilateral::_height = side; } generic <typename T> String ^ CSquare<T>::Describe() { return L"A square is a quadrilateral with four equal sides"; } generic <typename T> void CSquare<T>::ShowCharacteristics() { Console::WriteLine(L"Geometric Figure: {0}", this->Name); Console::WriteLine(L"Description: {0}", CQuadrilateral::Describe()); Console::WriteLine(L" {0}", this->Describe()); Console::WriteLine(L"Side: {0}", this->Base); } |
You can then declare a variable of the class and use it as we done so far for other generic classes. Here is an example:
Source File: Exercise.cpp |
#include "Quadrilateral.h" #include "Square.h" using namespace System; int main() { // Rectagle, in meters CSquare<Byte> ^ plate = gcnew CSquare<Byte>; plate->Name = L"Plate"; plate->Base = 15; plate->Height = 28; plate->ShowCharacteristics(); Console::WriteLine(); return 0; } |
This would produce:
Geometric Figure: Plate Description: A quadrilateral is a geometric figure with four sides A square is a quadrilateral with four equal sides Side: 15 Press any key to continue . . .
Introduction to Generics and Interfaces |
In the same way, you can create a generic interface that would serve as the base class of other generic classes. To proceed, when creating the interface, precede it with a generic<> declaration. Here is an example:
generic <typename T> public interface class IGeometry { property String ^ Name; void Display(); };
Since this is a generic interface, like an interface class, when deriving a class from it, follow the formula we reviewed for inheriting from a generic class. Here is an example:
generic <typename T> public interface class IGeometry { property String ^ Name; void Display(); }; generic <typename T> public ref class CRound : public IGeometry<T> { };
When implementing the derived class, you must observe all rules that apply to interface derivation. Here is an example:
using namespace System; generic <typename T> public interface class IGeometry { property String ^ Name; void Display(); }; generic <typename T> public ref class CRound : public IGeometry<T> { private: String ^ _name; public: CRound(); CRound(String ^ name); property String ^ Name { virtual String ^ get() { return _name; } virtual void set(String ^ value) { _name = value; } } virtual void Display(); }; generic <typename T> CRound<T>::CRound() { _name = L"Unknown"; } generic <typename T> CRound<T>::CRound(String ^ name) { _name = name; } generic <typename T> void CRound<T>::Display() { Console::WriteLine(L"Name: {0}", Name); } int main() { CRound<double> ^ rnd = gcnew CRound<double>; rnd->Name = L"General Round Shape"; rnd->Display(); Console::WriteLine(); return 0; }
This would produce:
Name: General Round Shape Press any key to continue . . .
In the same way, you can derive a generic class from another generic class that derived from a generic interface.
Imagine you create a regular interface such as the following:
public interface class IGeometry { property String ^ Name; void Display(); };
Then imagine you derive a regular class from it. Here is an example:
public ref class CRound : public IGeometry { private: String ^ _name; double _rad; public: CRound(); CRound(String ^ name); CRound(String ^ name, double radius); property String ^ Name { virtual String ^ get() { return _name; } virtual void set(String ^ value) { _name = value; } } property double Radius { double get() { return _rad; } void set(double value) { _rad = (value <= 0) ? 0.00 : value; } } virtual void Display(); }; CRound::CRound() { _name = L"Unknown"; } CRound::CRound(String ^ name) { _name = name; _rad = 0.00; } CRound::CRound(String ^ name, double radius) { _name = name; _rad = radius; } void CRound::Display() { Console::WriteLine(L"Name: {0}", Name); Console::WriteLine(L"Radius: {0}", Radius); }
You may be tempted to derive just any type of class from it. One of the features of generics is that you can create a class that must implement the functionality of a certain abstract class of your choice. For example, when creating a generic class, you can oblige it to implement the functionality of a certain interface or you can make sure that the class be derived from a specific base class. This would make sure that the generic class surely contains some useful functionality. This is the basis of generic constraints.
To create a constraint on a generic class, after the generic<> declaration, type where TypeName : followed by the rule that the class must follow. For example, you may want the generic class to implement the functionality of a pre-defined class. You can create the generic class as follows:
using namespace System; public interface class IGeometry { property String ^ Name; void Display(); }; public ref class CRound : public IGeometry { . . . }; . . . generic <typename T> where T : CRound public ref class CSphere { };
After creating the class, you must implement the virtual members of the where class, using the rules of generic classes, the way we have done it so far.
When declaring a handle for the generic class, in its <> operator, you must enter a handle to the base class. You must also make sure that the base class has a known value before using it. If the memory is not clearly allocated for the parameter type, you would receive an error.
Here is an example:
using namespace System; public interface class IGeometry { property String ^ Name; void Display(); }; public ref class CRound : public IGeometry { private: String ^ _name; double _rad; public: CRound(); CRound(String ^ name); CRound(String ^ name, double radius); property String ^ Name { virtual String ^ get() { return _name; } virtual void set(String ^ value) { _name = value; } } property double Radius { double get() { return _rad; } void set(double value) { _rad = (value <= 0) ? 0.00 : value; } } virtual void Display(); }; CRound::CRound() { _name = L"Unknown"; } CRound::CRound(String ^ name) { _name = name; _rad = 0.00; } CRound::CRound(String ^ name, double radius) { _name = name; _rad = radius; } void CRound::Display() { Console::WriteLine(L"Name: {0}", Name); Console::WriteLine(L"Radius: {0}", Radius); } generic <typename T> where T : CRound public ref class CSphere { private: T _t; public: CSphere(); CSphere(T fig); property T Figure { T get() { return _t; } void set(T value) { _t = value; } } }; generic <typename T> CSphere<T>::CSphere() { } generic <typename T> CSphere<T>::CSphere(T fig) { _t = fig; } int main() { CRound ^ rnd = gcnew CRound; rnd->Name = L"Circle"; rnd->Radius = 60.12; CSphere<CRound ^> ^sph = gcnew CSphere<CRound ^>; sph->Figure = rnd; Console::WriteLine(L"Circle Characteristics"); Console::WriteLine(L"Name: {0}", sph->Figure->Name); Console::WriteLine(L"Radius: {0}", sph->Figure->Radius); Console::WriteLine(); return 0; }
This would produce:
Circle Characteristics Name: Circle Radius: 60.12 Press any key to continue . . .
You can also create a constraint so that a generic class implements an interface. You would use the following:
using namespace System; public interface class IGeometry { property String ^ Name; void Display(); }; generic <typename T> where T : IGeometry public ref class CRound { };
|
|||
Previous | Copyright © 2006-2025, FunctionX | Saturday 29 April 2023, 22:44 | Next |
|