Return an object of random type in C#

Viewed 268

I have a factory class and I'd like it to be able to return an object of random type. The type should be chosen from a predefined list of types. So, something like:

    public class NeutralFactory : NPCFactory
    {
        private List<Type> humanoids = new List<Type> { typeof(Dwarf), typeof(Fairy), typeof(Elf), typeof(Troll), typeof(Orc) };
        private Random random = new Random();
        public Creature CreateHumanoid(int hp = 100)
        {
            int index = random.Next(humanoids.Count);
            return new humanoids[index]();
        }
    }

Sadly, this does not work.
I want to be able to pass arguments to the constructors, we can assume that they all have the same signature.

The only working way to do it I found is to use a switch statement and return a different object in each case:

        public Creature CreateHumanoid(int hp = 100)
    {
        int index = random.Next(humanoids.Count);
        switch (index)
        {
            case 0:
                return new Dwarf(hp);
            case 2:
                return new Fairy(hp);
            case 3:
                return new Elf(hp);
            case 4:
                return new Troll(hp);
            case 5:
                return new Orc(hp);
            default:
                throw new Exception("This should not execute.");
        }

    }

I don't really like it though. Is there a better way to do it?

EDIT:
Here's what I ended up using:

        private List<Func<int, string, Creature>> humanoids = new List<Func<int, string, Creature>> {
            (hp, name) => new Fairy(hp, name),
            (hp, name) => new Troll(hp, name),
        };

        private List<Func<int, Creature>> animals = new List<Func<int, Creature>> {

            (hp) => new Wolf(hp)
        };

        public override Creature CreateHumanoid(int hp = 100, string name = null)
        {
            int index = random.Next(humanoids.Count);
            return humanoids[index](hp, name);
        }

        public override Creature CreateAnimal(int hp = 100)
        {
            int index = random.Next(animals.Count);
            return animals[index](hp);
        }

I have found similar questions where () => function() syntax was used, but I did not understand it, I guess those are some kind of ad-hoc delegates? Anyways, it works as I wanted and is pretty concise.

Here's a helpful MSDN page on the Func<int, Creature> thing for any future readers:
https://docs.microsoft.com/en-us/dotnet/api/system.func-2?view=netframework-4.8

4 Answers

You can sore function in a List.

public class NeutralFactory: NPCFactory
    {
        private List<Func<int, Creature>> humanoids = new List<Func<int, Creature>> {
            hp=> new Dwarf(hp),
            hp=> new Fairy(hp),
            hp=> new Elf(hp),
            hp=> new Troll(hp),
            hp=> new Orc(hp)
        };
        private Random random = new Random();

        public Creature CreateHumanoid(int hp = 100)
        {
            int index = random.Next(humanoids.Count);
            return humanoids[index](hp);
        }
    }

You can use the method GetConstructor(type[]) to get the constructor which is most likely a Func object. If you use the Invoke method you get a object back. Last you have to parse it as Creature to return it.

public Creature CreateHumanoid( int hp = 100 ) {
        int index = random.Next( humanoids.Count );
        Type t = humanoids[index];
        return (Creature) t.GetConstructor( new Type[] { typeof( int ) } ).Invoke( new object[] { hp } );
    }

To avoid having to write creation functions for each class, you can use Activator.CreateInstance:

using System;
using System.Collections.Generic;

namespace so60998181
{
    public class Creature
    {
        public int hp;
        public Creature()
        {
            this.hp = 100;
        }
        public Creature(int hp)
        {
            this.hp = hp;
        }

        public override string ToString()
        {
            return String.Format("{0}, {1} hp", GetType().Name, hp);
        }
    }
    public class Dwarf : Creature
    {
        public Dwarf(int hp) : base(hp) { }
    }
    public class Fairy : Creature
    {
        public Fairy(int hp) : base(hp / 3) { }
    }
    public class Orc : Creature
    {
        public Orc(int hp) : base(hp * 4) { }
    }

    class Program
    {
        private static List<Type> humanoids = new List<Type> { typeof(Dwarf), typeof(Fairy), typeof(Orc) };
        private static Random rng = new Random();
        private static Creature CreateHumanoid(int hp = 100)
        {
            int index = rng.Next(humanoids.Count);
            var params = new object[] { hp };
            return Activator.CreateInstance(t, humanoids[index], null, params, null) as Creature;
        }
        static void Main(string[] args)
        {
            for (var i = 0; i < 10; i++)
            {
                Console.WriteLine(CreateHumanoid(i * 10));
            }
        }
    }
}

This outputs e.g.

Fairy, 0 hp
Orc, 40 hp
Fairy, 6 hp
Orc, 120 hp
Fairy, 13 hp
Orc, 200 hp
Fairy, 20 hp
Fairy, 23 hp
Fairy, 26 hp
Fairy, 30 hp

(poor fairy #1)

Sounds like a good case for an abstract class. If you don't ever want someone to create just an instance of 'Creature', and instead they should always create a version of a creature, then you want an abstract class. So you could do something like this:

public class NeutralFactory : NPCFactory
{
    private Random random = new Random();

    private List<Func<int, Creature>> humanoids = new List<Func<int, Creature>> {
        hp=> new Dwarf(hp),
        hp=> new Fairy(hp),
        hp=> new Elf(hp),
        hp=> new Troll(hp),
        hp=> new Orc(hp)
    };

    public Creature CreateHumanoid(int hp)
    {
        int index = random.Next(humanoids.Count);
        return humanoids[index](hp);
    }
}

public abstract class Creature
{
    public int hp { get; set; }

    public Creature(int hp)
    {
        this.hp = hp;
    }
}

You'd then use it like this:

public class Orc : Creature
{
    public Orc(int hp) : base(hp) { }
}

UPDATE

Added 'humanoids' outside of the create method to avoid creating a list of objects every time. Using a list of Func was taken from OxQ, though the purpose of using an abstract class like this is to ensure someone can't just instantiate 'Creature' on its own and you can use it to create base functionality as well.

Related