I want to create a generic function returning a collection of type ObservableCollection<T>^
. Callers pass a managed class type for type parameter T
.
Here is one of my tries of the generic function, after that explanations of other classes used:
generic<class T>
where T:CAnimal, gcnew()
ObservableCollection<T>^ GetItems(AnimalType animalType, int count)
{
ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
for (int i = 0; i < count; i++)
{
if (animalType == Dog)
{
CDog^ dog = gcnew CDog(i, count - i);
//CAnimal^ dog = gcnew CDog(i, count - i);
//T dog = gcnew CDog(i, count - i);
//T^ dog = gcnew CDog(i, count - i);
animals->Add(dog);
}
//else if (animalType == Cat) { ... }
//...
}
return animals;
}
Because I tried nearly 1632 ways to make that function work I can't tell you why I've implemented it as is :-/
Class CAnimal
is the base class for CDog
(assume there are more animals, too). AnimalType
is an enumeration intended to be used in the function above to determine the correct class type for instantiating and adding to the collection:
ref class CAnimal
{
public:
CAnimal(){}
CAnimal(int _age) { age = _age; }
int age;
};
ref class CDog : CAnimal {
public:
CDog(){}
CDog(int _age, int _other)
:CAnimal(age), other(_other) {}
int other;
};
enum AnimalType
{
Dog,
Cat,
Fish,
// ...
};
Furthermore there's a parent class holding ObservableCollection
's of dogs, cats, and so on:
ref class CZoo
{
private:
ObservableCollection<CDog^>^ dogs;
public:
CZoo()
{
dogs = GetItems<CDog^>(AnimalType::Dog, 3);
}
};
The compiler throws the following error:
error C2670: 'System::Collections::ObjectModel::Collection::Add' : the function template cannot convert parameter 1 from type 'CDog ^'
Can you tell me what I'm doing wrong?
!!! SOLUTION !!!
Based on David Yaws' answer I ended up moving the constructor parameters into a new function called Initialize
and removed the enum AnimalTypes
. The final code was changed to the following:
ref class CAnimal
{
internal:
virtual void Initialize(int _age, int _other)
{
age = _age;
other = _other;
}
public:
int age;
int other;
};
ref class CDog : CAnimal {};
ref class CCat : CAnimal {};
generic<class T>
where T:CAnimal, gcnew()
ObservableCollection<T>^ GetItems(int count)
{
ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
for (int i = 0; i < count; i++)
{
T animal = gcnew T();
animal->Initialize(i, count - i);
animals->Add(animal);
}
return animals;
}
ref class CZoo
{
private:
ObservableCollection<CDog^>^ dogs;
ObservableCollection<CCat^>^ cats;
public:
CZoo()
{
dogs = GetItems<CDog^>(3);
cats = GetItems<CCat^>(7);
}
};
If I remove the stuff not relevant to the error, here's what we've got:
generic<class T>
where T:CAnimal, gcnew()
ObservableCollection<T>^ GetItems()
{
ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
animals->Add(gcnew CDog());
return animals;
}
Now, what happens if this is invoked as GetItems<CCat^>()
? Now you're trying to put a CDog
into a list of CCat
, and that won't work. That's what the error message is saying: CDog
may not be able to be converted to T
.
If you want to do this, there's a couple possible solutions:
CAnimal
instead. CDog
is always valid to put into a list of CAnimal
.T
.I'd recommend the latter, and do something like this:
generic<class T>
where T:CAnimal, gcnew()
ObservableCollection<T>^ GetItems(int count)
{
ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
for (int i = 0; i < count; i++)
{
T animal = gcnew T();
animal->Initialize(i, count-i);
animals->Add(animal);
}
return animals;
}
gcnew()
constraint, that means that the class has a zero-parameter constructor, and you're allowed to call it using just type T
.CAnimal
. This way you can call it on any T
.GetItems<CDog^>(AnimalType::Cat, 3)
should do.