0

I am trying to generate an instance of a subclass depending on the given input, so I have created a dictionary that takes a System.Type as a key (although I will create an example using a string for easier understanding) and returns a System.Type as a value.

Something like this:

Dictionary<string, System.Type> types = new Dictionary<string, System.Type>()
    {
        { "Weapon", System.Type.GetType("WeaponClass") },
        { "Consumable", System.Type.GetType("ConsumableClass") },
        { "Resource", System.Type.GetType("ResourceClass") }
    };

WeaponClass, ConsumableClass and ResourceClass are subclasses of the same class, ItemClass.

So I would like to create a function that does something like this:

public ItemClass CreateItem(string itemName)
{
    System.Type type = types[itemName];
    // This is the part that I don't know how to make
    return new type();
}

This should return an instance of the corresponding subclass, but I don't know how to do it.

Can anyone help me out?

  • You can use something like ` switch(type) { case "Weapon": { return new WeaponClass(); } case "Consumable": { return new ConsumableClass(); } } ` – Hazrelle Oct 20 '21 at 10:39
  • I think you are looking for this: [How to create a new Object instance from a Type](https://stackoverflow.com/questions/752/how-to-create-a-new-object-instance-from-a-type) – J. S. Garcia Oct 20 '21 at 10:41
  • I currently have a similar implementation, but I'd rather have a Dictionary because it would make the code much cleaner and easier to upgrade. That's a good idea, though – Javier Riera Chirivella Oct 20 '21 at 10:41
  • The last thing you want to do in a game is to use dynamic creation via reflection –  Oct 20 '21 at 11:15
  • @JavierRieraChirivella a Dictionary and switch are actually more or less equivalent, except that in a dictionary you can add elements dynamically on runtime .... – derHugo Oct 20 '21 at 12:28
  • Is this actually about user input (-> don't allow users to type text but e.g. use an Enum dropdown instead) or how exactly are you going to use this? – derHugo Oct 20 '21 at 13:06

2 Answers2

7

You can use Activator.CreateInstance to create new objects of a particular type:

var type = types[itemName];
var item = Activator.CreateInstance(type);

However, I would suggest a slightly different approach. Instead of a dictionary containing types, make it a list of Funcs that give you the actual type. It's type-safe and has compile-time safety. For example:

var types = new Dictionary<string, Func<ItemClass>>()
{
    { "Weapon", () => new WeaponClass() },
    { "Consumable", () => new ConsumableClass() },
    { "Resource", () => new ResourceClass() }
};

Now your method can be:

public ItemClass CreateItem(string itemName)
{
    if(types.TryGetValue(nameOfType, out var factory))
    {
        return factory();
    }

    throw new Exception("Er, no idea what that type is");
}
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • This looks really good! I will try it out and mark this as the answer if it works for my use case. Thanks! – Javier Riera Chirivella Oct 20 '21 at 10:46
  • I would just add for the activator you need to cast since it returns an object so `var item = (ItemClass)Activator.CreateInstance(type);` and @OP you should rather use `typeof(WeaponClass)` etc instead of `GetType(string)` ... otherwise you could get into trouble if ever moving to different assemblies ;) But yeah the factory at the bottom is way better for many reasons ... type safety etc but you could also add parameters without getting into reflection hell ;) – derHugo Oct 20 '21 at 13:04
-1

Depending on your circumstances you might look into dependency injection. There is a neat little library called "Ninject", which I like to use. Basically it is a feature-rich wrapper for your dictionary. You have a so called kernel, which registers types with their implementations. In modules you can define, you have the ability to map implementations to their respective type:

public class DependencyInjectionModule : NinjectModule
{
    public override void Load()
    {
        Bind<IUserInterface>().To<UserInterface>();
        Bind<IMainForm>().To<MainForm>();
        Bind<ISplashForm>().To<SplashForm>();
        Bind<ISplashScreen>().To<SplashScreen>();
    }
}

Then you go and create a Kernel out of every module you created before:

var kernel = new StandardKernel(new DependencyInjectionModule());

After this, working with it is really nice - you get the kernel from wherever you stored it and go like this:

var userInterface = kernel.Get<IUserInterface>();
var mainForm = kernel.Get<IMainForm>();
var splashForm = kernel.Get<ISplashForm>();
var splashScreen = kernel.Get<ISplashScreen>();

Then you got the instances you wanted from an assembly, which doesn't even have to reference any of the projects which deliver the implementation.

Check it out: Ninject

Mister X
  • 29
  • 7
  • 1
    How exactly comes the conversion from a User input by string to an instance of according type which is what OP is asking about into play here? – derHugo Oct 20 '21 at 12:31
  • @derHugo Could you clarify this question any further? – Mister X Oct 20 '21 at 12:59
  • 1
    OP is asking "How can I take a `string` as input and return an instance of according type" ... how does your answer address this question? – derHugo Oct 20 '21 at 13:01
  • I agree with @derHugo here, this answer doesn't even come close to solving the problem OP has – DavidG Oct 20 '21 at 13:07
  • I pointed out the aspect on how OP could map a type with an alternative approach. Thats my contribution to the question. Whatever OP's circumstances may be, this could be an option, with or without string. If you use a string you could put it into an interface you map. Then if you load any instance, you can check the name there for example. – Mister X Oct 20 '21 at 13:17