Search code examples
c#oopassociationsaggregation

Instantiating User Defined Object inside Another Object in C#


I need some help with the way I am storing objects/data inside my application. I am creating an application that: creates a robot, assigns tasks to robot, displays the robot information with task time etc..

I have setup multiple different objects to be instantiated in Main when the program is run. Then the user will select a robot type from the list of types. I am having trouble passing the botType object into the Robot object. I am asking the user to select a botType 1-6 using int, then I would like the user selected int to define which botType should be applied to the Robot. Because I am initializing the BotTypes in Main, then using a different method to CreateRobot(). I am having trouble passing the botType into the Robot object. I can pass the integer the user selects but this is not passing the botType into Robot like I am trying to complete..

Here are my classes/constructors being used:

  public class Robot
    {
        //Store robot name
        public string botName { get; set; }

        //Store robot type
        public BotType botType { get; set; }

        //Store time to complete task
        public int timeElapsed { get; set; }

        public Robot(string BotName, BotType botType, int TimeElapsed)
        {
            this.botName = BotName;
            this.botType = new BotType();
            timeElapsed = TimeElapsed;
        }

    public class BotType
        {
    
            //Type of robot
            public string TypeName { get; set; }
            //Type of task represented by number
            public int TaskType { get; set; }
    
            //contructor to set values
            public BotType (string typeName, int taskType)
            {
                TypeName = typeName;
                TaskType = taskType;
            }

Then my main method where the objects are being initialized but when I try to use them, I am unable to convert user defined int into BotType like I would like..

public class BotOMat
    {
        public static List<Robot> botList = new List<Robot>();
        public static List<BotTask> botTaskMap = new List<BotTask>();
        public static List<BotType> botTypeMap = new List<BotType>();


        static void Main(string[] args)
        {
            //initalize bot types
            BotType UNIPEDAL = new BotType("Unipedal", 1);
            BotType BIPEDAL = new BotType("Bipedal", 2);
            BotType QUADRUPEDAL = new BotType("Quadrupedal", 3);
            BotType ARACHNID = new BotType("Arachnid", 4);
            BotType RADIAL = new BotType("Radial", 5);
            BotType AERONAUTICAL = new BotType("Aeronautical", 6);

            //initialize bot tasks
            BotTask DISHES = new BotTask("Do the dishes", 1000, 0);
            BotTask SWEEP = new BotTask("Sweep the house", 3000, 0);
            BotTask LAUNDRY = new BotTask("Do the laundry", 10000, 0);
            BotTask RECYCLING = new BotTask("Take out the recycling", 4000, 0);
            BotTask SAMMICH = new BotTask("Make a sammich", 7000, 0);
            BotTask LAWN = new BotTask("Mow the lawn", 20000, 0);
            BotTask RAKE = new BotTask("Rake the leaves", 18000, 0);
            BotTask BATH = new BotTask("Give the dog a bath", 14500, 0);
            BotTask BAKE = new BotTask("Bake some cookies", 8000, 0);
            BotTask WASH = new BotTask("Wash the car", 20000, 0);

            var botTaskMap = new List<BotTask> { DISHES, SWEEP, LAUNDRY, RECYCLING, SAMMICH, LAWN, RAKE, BATH, BAKE, WASH };
            var botTypeMap = new List<BotType> { UNIPEDAL, BIPEDAL, QUADRUPEDAL, ARACHNID, RADIAL, AERONAUTICAL };

private static void createRobot()
        {

            //Get robot name, add to list saving multiple names.
            Console.WriteLine("Enter robot name:");
            string botName = Console.ReadLine();

            //Get robot type
            Console.WriteLine("Enter robot type: (number)");
            int botType = Convert.ToInt32(Console.ReadLine());
            //botType = botTypeMap[botType];
            
            //boxing to convert int to BotType

            //error handling 
            if (botType < 1 || botType > 6)
            {
                Console.WriteLine("Invalid input. Please enter number 1-6.");
                //BotType.TaskType = 0;
            }

            //Add robot to the class storing robot information.
            if (botType > 1 || botType < 6)
            {
                
                Robot aRobot = new Robot(botName, botType, 0);
                botList.Add(aRobot);
                aRobot.AssignBotTask();
                aRobot.CompleteTask();

            }
            else
            {
                MainMenu();
            }

        }

I can either pass BotType into Robot as an Integer or receive an Arguement 2: cannot convert from int to BotOMat.BotType. Whenever I try to write any output using aRobot the console is writing using the protected variables which is not the desired output.

Should I be creating aRobot in the Robot class? I then need to assign BotTask(s) to the robot later on... I believe these use an Association type relationship but if the objects are not initialized in each class. I am unsure how to initiate; for example: a Unipedal Robot that does the Dishes.

I appreciate any help in advance. I tried to be as descriptive as possible without posting a repeat question as any other example I can find is much more basic than what I believe I am trying to accomplish here.


Solution

  • The next time you post a question, you should take a look at the help, particularly in regards to preparing a Minimally Reproduceable Example. Your code doesn't compile, you are missing types, functions, etc. Some of what I'm going to show below doesn't quite comport to what you show, but it certainly seems to meet your intention (at least to me).

    I'm going to start at the bottom layer and work up. Your BotType class simply has a string and an int (with the ints incrementing). Instead of using a class, I'm going to use an enum (you should read up on these). Enums are value types. Under the covers, they end up being represented by a simple integral-valued type (like int), but with the metadata they include, they have a symbolic name as well. So:

    public enum RobotType
    {
        Unipedal = 1,
        Bipedal,
        Quadrupedal,
        Arachnid,
        Radial,
        Aeronautical,
    }
    

    Had I not included the =1, then Unipedal would have started with the default value (zero). You'll see why starting at one makes sense below. The nice thing about enums is that you can convert them to an integer, or to a string that represents their symbol. You can also parse either a string containing an integer or a string containing symbolic name to an enum instance:

    var asInt = (int) RobotType.Unipedal;
    var asString = RobotType.Unipedal.ToString();
    bool isGoodParse = Enum.TryParse<RobotType>("1", out RobotType parsedFromInt);
    isGoodParse = Enum.TryParse<RobotType>("Unipedal", out RobotType parsedFromString);
    

    All that code works the way you might expect.

    It's tempting to just do the same thing with your task types. But the names aren't valid C# symbols (they have spaces). Instead, we'll use System.ComponentModel.DescriptionAttribute to add some extra metadata to the enum values:

    public enum RobotTaskName
    {
        [Description("Do the dishes")]
        DoTheDishes = 1,
        [Description("Sweep the house")]
        SweepTheHouse,
        [Description("Do the laundry")]
        DoTheLaundry,
        [Description("Take out the recycling")]
        TakeOutTheRecycling,
        [Description("Make a sammich")]
        MakeASammich,
        [Description("Mow the lawn")]
        MowTheLawn,
        [Description("Rake the leaves")]
        RakeTheLeaves,
        [Description("Give the dog a bath")]
        GiveTheDogABath,
        [Description("Bake some cookies")]
        BakeSomeCookies,
        [Description("Wash the car")]
        WashTheCar,
    }
    

    But, now we can't just call ToString on the enum value to get the corresponding name. Instead, we'll use reflection to dig the name out of the Description attribute:

    public static class RobotExtensions
    {
        public static string GetDescription<T>(this T enumValue) where T : struct, Enum
        {
            var enumInfo = typeof(T).GetField(enumValue.ToString());
            if (enumInfo != null)
            {
                var attributes = enumInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attributes != null && attributes.Length > 0)
                {
                    return ((DescriptionAttribute[])attributes)[0].Description;
                }
            }
            //otherwise
            return enumValue.ToString();
        }
    }
    

    That code declares an Extension Method for enums (you can call it directly on an enum - any enum). It gets reflected Field Information for the enum value, checks to see if it has an DescriptionAttribute. If it does, it digs out the description and returns it. If it can't get that information, it simply returns enumValue.ToString. As a result:

    RobotTaskName.DoTheDishes.GetDescription();   //returns "Do the dishes"
    RobotType.Arachnid.GetDescription();          //returns "Arachnid"
    

    The other cool thing about doing it this way, is that we can write some code that prompts the user for a particular enum value, checks it's validity, etc:

    public static T? GetResponseUsingEnum<T>(string prompt) where T : struct, Enum
    {
        //Loop until a good answer (or no answer)
        while (true)
        {
            Console.WriteLine($"{prompt}: Please enter one of:");
            var values = (T[])Enum.GetValues(typeof(T));
            foreach (var enumValue in values)
            {
                var description = enumValue.GetDescription<T>();
                var intValue = Convert.ToInt32(enumValue);
                Console.WriteLine($"{intValue}: {description}");
            }
            Console.Write(">> ");
            var response = Console.ReadLine();
            if (string.IsNullOrEmpty(response))
            {
                return (T?)null;
            }
            if (Enum.TryParse<T>(response, out var val))
            {
                if (values.Contains(val))
                {
                    Console.WriteLine($"You answered: {val}");
                    return val;
                }
            }
        }
    }
    

    That code shows the integer and string representations of all of the values of a particular enumerated type, and then prompts the user to enter a value. If the user enters a value out of range, then it re-prompts him/her. If the user just hits Enter, then it returns a null value. Otherwise, if the user enters a valid answer, it returns the answer (not as an integer, but as a properly typed enumerated item.

    Now that the bottom layer is complete, let's work up ...

    I couldn't quite tell what your Robot Task type does, or what the extra two integers where, so I called them Num1 and Num2. I also added a Perform function that "performs" the task (echoes it to the console).

    public class BotTask
    {
        public RobotTaskName Name { get; set; }
        public string Description => Name.GetDescription();
        public int Num1 { get; set; }
        public int Num2 { get; set; }
    
        public BotTask(RobotTaskName name, int num1, int num2)
        {
            Name = name;
            Num1 = num1;
            Num2 = num2;
        }
    
        public void Perform()
        {
            Console.WriteLine($"  - Peforming Task: {Name.GetDescription()} with {Num1} and {Num2}");
        }
    }
    

    If you are curious, the Description property is a read-only property that calls GetDescription on the underlying RobotTaskName enumerated type.

    Then I recreated your Robot type. I don't know what your intention was for the TimeElapsed property. But, I changed it to a TimeSpan from an int - because, well, that's what TimeSpans are for.

    public class Robot
    {
        public string BotName { get; set; }
    
        public RobotType BotType { get; set; }
        public string BotTypeDescription => BotType.GetDescription();
    
        public TimeSpan TimeElapsed { get; set; }
    
        private List<BotTask> _tasks = new List<BotTask>();
        public IEnumerable<BotTask> Tasks => _tasks;
    
        public Robot(string botName, RobotType botType, TimeSpan timeElapsed = default)
        {
            this.BotName = botName;
            this.BotType = botType;
            TimeElapsed = timeElapsed;
        }
    
        public void AddTask (BotTask task)
        {
            _tasks.Add(task);
        }
    
        public void Show()
        {
            Console.WriteLine($"Robot: {BotName}, Type: {BotTypeDescription}, TimeElapsed: {TimeElapsed}");
            foreach (var task in Tasks)
            {
                task.Perform();
            }
        }
    }
    

    Note that I also added a list of tasks that have been assigned to each robot. That includes the list, an AddTask method to add tasks to a robot, etc.

    Finally, I added a Run method to the Robot class that fires the whole thing off (call it from Main). It allows you to create more than one robot and to assign more than one task to each robot. It uses the GetResponseUsingEnum method to get both the robot type and the task type. This means a consistent user interface and some healthy code reuse (if you find a bug in that function, you fix two pieces of functionality).

    public static void Run()
    {
        var robotsList = new List<Robot>();
        //loop until there are no more robots
        while (true)
        {
    
            Console.Write("Enter robot name: ");)
            var robotName = Console.ReadLine();
            if (string.IsNullOrEmpty(robotName))
            {
                break;  //empty robot, time to list things out
            }
    
            RobotType? robotType = null;
            while (!robotType.HasValue)
            {
                robotType = GetResponseUsingEnum<RobotType>("Robots");
            }
    
            var robot = new Robot(robotName, robotType.Value);
            robotsList.Add(robot);
            Console.WriteLine("Time to add some tasks for this robot");
    
            //get tasks - loop until no more tasks
            while (true)
            {
                var taskName = GetResponseUsingEnum<RobotTaskName>("RobotTaskName");
                if (!taskName.HasValue)
                {
                    break;  //no more tasks
                }
                var task = new BotTask(taskName.Value, 100, 200);
                robot.AddTask(task);
            }
        }
    
        //At this point, we have a fully populated list of robots, each with some tasks
        foreach (var robot in robotsList)
        {
            robot.Show();
        }
    }
    

    Finally, if you run it, the output looks like this:

    Enter robot name: Robby
    Robots: Please enter one of:
    1: Unipedal
    2: Bipedal
    3: Quadrupedal
    4: Arachnid
    5: Radial
    6: Aeronautical
    >> 2
    You answered: Bipedal
    Time to add some tasks for this robot
    RobotTaskName: Please enter one of:
    1: Do the dishes
    2: Sweep the house
    3: Do the laundry
    4: Take out the recycling
    5: Make a sammich
    6: Mow the lawn
    7: Rake the leaves
    8: Give the dog a bath
    9: Bake some cookies
    10: Wash the car
    >> 2
    You answered: SweepTheHouse
    RobotTaskName: Please enter one of:
    1: Do the dishes
    2: Sweep the house
    3: Do the laundry
    4: Take out the recycling
    5: Make a sammich
    6: Mow the lawn
    7: Rake the leaves
    8: Give the dog a bath
    9: Bake some cookies
    10: Wash the car
    >> 3
    You answered: DoTheLaundry
    RobotTaskName: Please enter one of:
    1: Do the dishes
    2: Sweep the house
    3: Do the laundry
    4: Take out the recycling
    5: Make a sammich
    6: Mow the lawn
    7: Rake the leaves
    8: Give the dog a bath
    9: Bake some cookies
    10: Wash the car
    >>
    Enter robot name: SecondRobot
    Robots: Please enter one of:
    1: Unipedal
    2: Bipedal
    3: Quadrupedal
    4: Arachnid
    5: Radial
    6: Aeronautical
    >> 3
    You answered: Quadrupedal
    Time to add some tasks for this robot
    RobotTaskName: Please enter one of:
    1: Do the dishes
    2: Sweep the house
    3: Do the laundry
    4: Take out the recycling
    5: Make a sammich
    6: Mow the lawn
    7: Rake the leaves
    8: Give the dog a bath
    9: Bake some cookies
    10: Wash the car
    >> 8
    You answered: GiveTheDogABath
    RobotTaskName: Please enter one of:
    1: Do the dishes
    2: Sweep the house
    3: Do the laundry
    4: Take out the recycling
    5: Make a sammich
    6: Mow the lawn
    7: Rake the leaves
    8: Give the dog a bath
    9: Bake some cookies
    10: Wash the car
    >> 10
    You answered: WashTheCar
    RobotTaskName: Please enter one of:
    1: Do the dishes
    2: Sweep the house
    3: Do the laundry
    4: Take out the recycling
    5: Make a sammich
    6: Mow the lawn
    7: Rake the leaves
    8: Give the dog a bath
    9: Bake some cookies
    10: Wash the car
    >>
    Enter robot name:
    Robot: Robby, Type: Bipedal, TimeElapsed: 00:00:00
      - Peforming Task: Sweep the house with 100 and 200
      - Peforming Task: Do the laundry with 100 and 200
    Robot: SecondRobot, Type: Quadrupedal, TimeElapsed: 00:00:00
      - Peforming Task: Give the dog a bath with 100 and 200
      - Peforming Task: Wash the car with 100 and 200