static void Main()
        {
            #region object definition
            Person student = new Student();
            student.SetInformationFromInput();
            student.PrintDetails();

            Person teacher = new Teacher();
            teacher.SetInformationFromInput();
            teacher.PrintDetails();

            UProgram uProgram = new UProgram();
            uProgram.SetInformationFromInput();
            uProgram.PrintDetails();

            Course course = new Course();
            course.SetInformationFromInput();
            course.PrintDetails();
            #endregion

            #region call method with NotImplementedException
            DateTime birthDate;
            string birthDateString = "12/01/1900"; // mm/dd/yyyy
            student.ValidatePersonBirthdate(birthDateString, out birthDate);
            #endregion
        }
        public void SetInformationFromInput()
        {
            const string COURSE_NAME_USER_PROMPT       = "Please enter the course name:       ";
            const string CREDITS_USER_PROMPT           = "Please enter the number of credits: ";
            const string DURATION_IN_WEEKS_USER_PROMPT = "Please enter the duration in weeks: ";
            const string TEACHER_INFO_USER_PROMPT      = "Please enter information about course's teacher. ";

            /*
                    If it throws during input gathering, we want to make
                    sure, our Student object will not be corrupted.
                    We could define new variables to save user input to,
                    and then after we were fairly confident that input was
                    good to go, we would set it to object's fields, however...

                    But what if, though highly unlikely, setters throw?
                    That would leave our object half-mutated and with corrupt
                    data. Fortunately, we can pay the price of caching
                    object's fields and simply restore object's values in
                    the catch block if something does go wrong. Hence,
                    no need to define new variables just to collect user data.
               */

            string  nameFieldCopy            = Name;
            int     creditsFieldCopy         = Credits;
            int     durationInWeeksFieldCopy = DurationInWeeks;

            try
            {

                #region input gathering

                Console.WriteLine(COURSE_NAME_USER_PROMPT);
                Name = Console.ReadLine();

                Console.WriteLine(CREDITS_USER_PROMPT);
                Credits = Int32.Parse( Console.ReadLine() );

                Console.WriteLine(DURATION_IN_WEEKS_USER_PROMPT);
                DurationInWeeks = Int32.Parse( Console.ReadLine() );

                Console.WriteLine(TEACHER_INFO_USER_PROMPT);

                // delegate input collection and data restoration on throw
                // for Teacher to Teacher's own method
                Teacher = new Teacher();
                Teacher.SetInformationFromInput();

                #endregion

            }
            catch {

                #region restoring fields

                Name = nameFieldCopy;
                Credits = creditsFieldCopy;
                DurationInWeeks = durationInWeeksFieldCopy;
                // Teacher restoration is delegated to its own SetInformationFromInput method

                #endregion

                throw;
            }
        }