Stopwatch exception implementation in C# I'm new to C# programming and has been following a certain intermediate tutorial that has asked me to write a stopwatch simulation code that includes an exception system:


  Exercise 1: Design a Stopwatch
  
  Design a class called Stopwatch. The job of this class is to simulate a stopwatch. It should provide two methods: Start and Stop. We call the start method first, and the stop method next. Then we ask the stopwatch about the duration between start and stop. Duration should be a value in Timespan. Display the duration on the console.
  
  We should also be able to use a stopwatch multiple times. So we may start and stop it and then start and stop it again. Make sure the duration value each time is calculated properly.
  
  We should not be able to start a stopwatch twice in a row (because that may overwrite the initial start time). So the class should throw an InvalidOperationException is it's started twice.
  
  Educational tip: The aim of this exercise is to make you understand that a class should be always in a valid state. We use encapsulation and information hiding to achieve that. The class should not reveal its implementation detail. It only reveals a little bit, like a black box. From the outside, you should not be able to misuse a class because you shouldn't be able to see the implementation detail.


Transcribed from:





I have implemented code that does this exactly, however, I want to learn how to optimize my code to use fewer lines and make it more readable.

using System;

namespace Stopwatch_Training
{
    class Program
    {
        public class Stopwatch
        {
            private DateTime _startTime;
            private DateTime _stopTime;
            private TimeSpan _duration;
            public DateTime StartTime()

            {
                _startTime = DateTime.Now;
                return _startTime;
            }

            public DateTime Stoptime()

            {
                _stopTime = DateTime.Now;
                return _stopTime;
            }
        }

        static void Main(string[] args)

        {
            var stopwatch = new Stopwatch();
            int input1;
            DateTime starTime;
            DateTime endTime;
            double duration;
            int counter = 0;

            while (true)

            {
                Console.Write("1-Start Timer\n2-Stop Timer\n");
                input1 = Convert.ToInt32(Console.ReadLine());
                if (input1 == 1)
                {
                    starTime = stopwatch.StartTime();
                    counter = 1;
                }
                else
                {
                    throw (new InvalidOperationException("Please Start the timer with 1"));
                }

                Console.WriteLine("Please enter '2' to stop the timer whenever you want");
                input1 = Convert.ToInt32(Console.ReadLine());
                if (input1 == 2)
                {
                    endTime = stopwatch.Stoptime();
                    counter = 0;

                }

                else
                {
                    throw (new InvalidOperationException("Please end the timer with 2"));
                }
                if (counter == 1)
                {
                    throw (new InvalidOperationException("stuff"));
                }
                duration = (endTime - starTime).TotalSeconds;
                if (duration &gt; 60.0)
                {
                    duration = (endTime - starTime).TotalMinutes;
                    Console.WriteLine(duration + "m");
                }
                else
                {

                    Console.WriteLine(duration + "s");
                }

            }
        }
    }
}
You have the beginning of a stopwatch representation—you have its data. You still need to add its behavior.

Approach your design of the class as if the class should stand on its own. Input/output will be handled by something else, like the console, but the stopwatch should be the authority on its internal state.

The simplest stopwatch has a start/stop button and a display. We'll interpret "having a display" to mean "a way to get the stored elapsed time".

This means we'll need to provide three methods:


Start: Sets the stopwatch in a running state.
Stop: Sets the stopwatch in an idle state.
TimeElapsed: Reads out the currently elapsed time.


Additionally, Start and Stop have the requirement that they should throw InvalidOperationException when the stopwatch is not a compatible state. Our stopwatch will need a notion of running to do this.

Let's look how we can modify the stopwatch class to adopt this behavior. I've annotated some below:

    public class Stopwatch
    {
        private DateTime _startTime; // we need this
        private DateTime _stopTime; // ? either this or duration
        private TimeSpan _duration;
        public DateTime StartTime() // why do we return a value here?

        {
            // we still need to check whether we are already running
            _startTime = DateTime.Now;
            return _startTime;
        }

        public DateTime Stoptime() // why do we return a value here?

        {
            // we still need to check whether we are still running
            _stopTime = DateTime.Now;
            return _stopTime;
        }

        // We still need the notion of 'currently running'
    }

    // First attempt
    public class Stopwatch
    {
        private DateTime _startTime;
        private DateTime _stopTime;
        private Boolean _running;

        public void StartTime()
        {
            if ( _running )
            {
                throw new InvalidOperationException("Cannot start: already running");
            }
            _startTime = DateTime.Now;
            _running = true;
        }

        public void Stoptime()
        {
            if ( !_running )
            {
                throw new InvalidOperationException("Cannot stop: not running");
            }

            _stopTime = DateTime.Now;
            _running = false;
        }

        public TimeSpan ElapsedTime()
        {
            if ( _running )
            {
                return DateTime.Now - _startTime;
            }
            else
            {
                return _stopTime - _startTime;
            }
        }
    }


Note that we check if the stopwatch is currently running when asking for elapsed time: otherwise, we're going to get a very strange reading.

Now we can 'clean up' a little bit to follow conventions:


Class members are implicitly private, so we don't need to add the private modifier.
Members should start with a capital letter.


Which gives us:

    public class Stopwatch
    {
        DateTime StartTime;
        Boolean IsRunning;
        DateTime StopTime;

        public void Start()
        {
            if (IsRunning)
            {
                throw new InvalidOperationException("Cannot start: already running");
            }
            StartTime = DateTime.Now;
            IsRunning = true;
        }

        public void Stop()
        {
            if (!IsRunning)
            {
                throw new InvalidOperationException("Cannot stop: not running");
            }
            IsRunning = false;
            StopTime = DateTime.Now;
        }

        public TimeSpan ElaspedTime()
        {
            if (IsRunning)
            {
                return DateTime.Now - StartTime;
            }
            else
            {
                return StopTime - StartTime;
            }
        }
    }


And a quick Main to play around with it:

    static void Main(string[] args)
    {
        var stopwatch = new Stopwatch();
        int choice;

        do
        {
            Console.Write("1-Start Timer\n2-Stop Timer\n3-Read Timer\n0-Quit\n");
            choice = Convert.ToInt32(Console.ReadLine());
            switch (choice)
            {
                case 1: stopwatch.Start(); break;
                case 2: stopwatch.Stop(); break;
                case 3: Console.WriteLine(stopwatch.ElaspedTime()); break;
            }
        } while (choice != 0);
    }