Yes, it is possible, but not the way you described it in your question. First of all “a type” like “GameObject” for example is just the name of a type that is recognised by the compiler to identify that type. It’s not possible to “store” a reference to such a “name” in a variable because that name doesn’t have an actual type. The compiler statically links the correct type with the “new” operator. Therefore you can’t use “new” with a variable.
You might say: wait, there are generics. Yes, but generic parameters are nothing like variables. They are actually like alias names or placeholders for the type you insert when you use a generic class / method. Generic types allows you to replace such a fix-wired type at runtime with a concrete type. Since there’s no variable “type” to store that information, it doesn’t work that way. The solution is the System.Type class.
The System.Type class is part of the reflection system. It is used to describe other types. For every type there is exactly one System.Type object instance that describes that type. Even the System.Type class itself has an instance of System.Type that describes itself. The Type class contains properties which tell you if that type is a generic type, an array type, a class, a struct, an interface, (…). It also has methods to enumerate the members of that type and allows you to inspect them. In C# the “typeof” operator returns the System.Type instance for a given type. This: typeof(GameObject)
would return the System.Type object that describes the GameObject class. In UnityScript there is no typeof operator but UnityScript automatically / implicitly converts a type into it’s System.Type object based on the usage.
The Unity API uses the System.Type type quite a lot. The non generic version of AddComponent for example takes a System.Type object to add that class described by the instance to the gameObject. The generic and the string version simply use the System.Type version in the end.
Since the System.Type class is an ordinary class, you can put references to instances of that class into a List or an array without any problems. However as said earlier you can’t use the new operator with variables. The usual way using reflection to create an instance of a class described by a Type object is quite cumbersome as you would have to iterate through the available constructor methods, pick the one that suits your needs and call it via reflection. However the reflection system provides a class that simplifies the creation of a class. It’s called Activator
The Activator class provides a static method CreateInstance which takes a System.Type reference and will create and return a new instance of the described type. It will simply call the default constructor if one is available. There are also a couple of overloads which allows you to use private constructors or to additionally pass a list of parameters. CreateInstance will try to find a constructor that matches your parameter list.
Here’s an example using a 3 dimensional array to store System.Type objects and how to create an instance of one of those types:
System.Type[,,] types = new System.Type[3,4,5];
// you have to initialize your array somehow:
types[0,0,3] = typeof(SomeClassDerivedFromBaseClass);
types[0,0,4] = typeof(SomeOtherClassDerivedFromBaseClass);
//[ ... ]
BaseClass CreateInstance(int i1, int i2, int i3)
{
return (BaseClass)System.Activator.CreateInstance(types[i1,i2,i3]);
}
Note: The reflection system always work with System.Objects. So the reference that CreateInstance return is always of type System.Object. In my example i simply cast it to our BaseClass type. This cast will of course fail and throw an exception if you put a type in that types array that is not derived from BaseClass. You can also use refection to get all types that are derived from a certain base class.
As final note i should mention that reflection is not the usual way to work with classes and objects. Reflection actually breaks almost all OOP rules and is in most cases way slower than using the normal statically typed way. However in some cases it’s difficult or impossible to get the same results with pure OOP. Using the factory pattern as Unitraxx suggested is quite verbose and requires much more work to implement, but it’s more type-safe and usually faster at runtime.