Search code examples
c#polymorphism

C# Randomize inherited class without hardcoding


What I want to achieve is to by either name of derived class or enum name generate a random derived class. While my code works in this case, it will require a lot of hardcoding if i decide to expand on it and to me it seems like a bad solution.

Here is my example code: (the UnitTypes variable includes all derived class names dynamically, so i feel like it may be useful - but I could figure out how to.)

public void Run()
        {

            var UnitTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.BaseType == typeof(Unit));
            Unit NewMonster = new Unit(); //<---

        konny: //for testing things out.

            int RandomType = new Random().Next(0, UnitTypes.Count());

            switch (RandomType)
            {
                case 0:
                    NewMonster = new Dingo();
                    break;
                case 1:
                    NewMonster = new Buffalo();
                    break;
                case 2:
                    NewMonster = new Dog();
                    break;
            }
            Console.WriteLine($"A wild {NewMonster.Name} appears");
            NewMonster.Attack();
            Console.ReadLine();

            goto konny;            
        }

And the Baseclass + 1 derived for sake of example.

    public class Unit
    {
        public Enemies Name { get; set; }

        public enum Enemies
        {
            Dingo, Buffalo, Dog
        }

        public virtual void Attack()
        {
            //nothing to see here
        }
   
    }

    class Dingo : Unit
    {

        public Dingo()
        {
            Name = Enemies.Dingo;
        }

        public override void Attack()
        {
            Console.WriteLine($"{Name}  gnaws at your brain");
        }

    }

Solution

  • How about something like this. First create a base class (I have no idea why you named it Unit, I named mine BaseAnimal):

    namespace AnimalsTest
    {
        public abstract class BaseAnimal
        {    
            public enum EnemiesTypes
            {
                Dingo, Buffalo, Dog
            }
    
            public abstract IEnumerable<EnemiesTypes> Enemies { get; }
    
            public abstract void Attack();
        }
    }
    

    Note that it's an abstract (non-instantiable) and that any sub-classes need to implement an Enemies property getter and an Attack method.

    Then I created three subclasses (in the same namespace):

    class Dingo : BaseAnimal
    {
        public override IEnumerable<EnemiesTypes> Enemies =>
            new List<EnemiesTypes> { EnemiesTypes.Buffalo, EnemiesTypes.Dog };
    
        public override void Attack()
        {
            Console.WriteLine($"{nameof(Dingo)} Gnaws at your brain");
        }
    }
    
    class Buffalo : BaseAnimal
    {
        public override IEnumerable<EnemiesTypes> Enemies =>
            new List<EnemiesTypes> { EnemiesTypes.Dog };
    
        public override void Attack()
        {
            Console.WriteLine($"{nameof(Buffalo)} Runs you down");
        }
    }
    
    class Dog : BaseAnimal
    {
        public override IEnumerable<EnemiesTypes> Enemies =>
            new List<EnemiesTypes> { EnemiesTypes.Buffalo };
    
        public override void Attack()
        {
            Console.WriteLine($"{nameof(Dog)} Barks at you");
        }
    }
    

    These are very simple classes, you could make them more complicated.

    Finally a test routine that exercises what I think you want. I get the names of the classes from the values of an enum and instantiate them at will.

    private static readonly Random Random = new Random();
    public static void Test()
    {
        var animalClasses = Enum.GetNames(typeof(EnemiesTypes)).Select(n => n.ToString()).ToArray();
        var thisAssembly = Assembly.GetExecutingAssembly().FullName;
    
        for (var i = 0; i < 6; ++i)
        {
            var className = nameof(AnimalsTest) + "." + animalClasses[Random.Next(0, animalClasses.Length)];
            var animal = (BaseAnimal) Activator.CreateInstance(thisAssembly, className).Unwrap();
            animal.Attack();
            Console.WriteLine($"{animal.GetType().Name} has enemies: {string.Join(", ",animal.Enemies.Select(e=>e.ToString()))}");
            Console.WriteLine();
            ;
        }
    }
    

    Note that the test code has no clue about the names of the classes it's working with, it pulls it all from the values of the enum.

    Each time I run this, I get a different result (because of, well, Random). But, here's the output of a typical run:

    Buffalo Runs you down
    Buffalo has enemies: Dog
    
    Dog Barks at you
    Dog has enemies: Buffalo
    
    Buffalo Runs you down
    Buffalo has enemies: Dog
    
    Dog Barks at you
    Dog has enemies: Buffalo
    
    Dingo Gnaws at your brain
    Dingo has enemies: Buffalo, Dog
    
    Dingo Gnaws at your brain
    Dingo has enemies: Buffalo, Dog