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++ 2005, 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++ 2005, 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
{
};
No comments:
Post a Comment