Home

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 . . .
 

Previous Copyright © 2006-2007 FunctionX, Inc. Next