Methods for making switch cases smaller and less redundant

OgreVorbis

Member
Joined
Mar 24, 2023
Messages
5
Location
New England
Programming Experience
5-10
So I've now encountered this problem enough times during programming and just brushed it off, but I want to increase my code quality, so I'd like to discuss this.
One of the issues with switch cases is that I often times have nearly identical code for each section, but just calling a different method. Furthermore, I have multiple different switch cases using the same case strings. Maybe in some instances it would be extreme enough that I could just somehow map strings to method names (don't know how).

Here's what I'm currently working on:

C#:
private bool TimeCalc(string theParam, TimeSpan span, out string answer)
{
    answer = "";
    switch (theParam)
    {
        case "days":
            answer = span.TotalDays.ToString();
            break;
        case "hours":
            answer = span.TotalHours.ToString();
            break;
        case "mins":
        case "minutes":
            answer = span.TotalMinutes.ToString();
            break;
        case "secs":
        case "seconds":
            answer = span.TotalSeconds.ToString();
            break;
        case "weeks":
            answer = ((span.TotalDays) / 7.0).ToString();
            break;
        case "months":
            answer = ((span.TotalDays) / 30.437).ToString();
            break;
        case "years":
            answer = ((span.TotalDays) / 365.0).ToString();
            break;
        default:
            return false;
    }

    return true;
}

private bool FuturePastCalc(string theParam, DateTime startDate, string num, out string answer)
{
    answer = "";
    switch (theParam)
    {
        case "days":
            answer = startDate.AddDays(Convert.ToDouble(num)).ToString();
            break;
        case "hours":
            answer = startDate.AddHours(Convert.ToDouble(num)).ToString();
            break;
        case "mins":
        case "minutes":
            answer = startDate.AddMinutes(Convert.ToDouble(num)).ToString();
            break;
        case "secs":
        case "seconds":
            answer = startDate.AddSeconds(Convert.ToDouble(num)).ToString();
            break;
        default:
            return false;
    }
    
    return true;
}

// AND MORE IN SEPARATE METHODS

So firstly, I suppose I could create an enum, dictionary, or maybe an enum + array.
So maybe have an array like:
C#:
string[] commands = new string[] {"days", "hours", "mins", ... };

enum TimeChunk
{
     Days, Hours, Mins, Secs, ...
}

// Then calling would be like:

case commands[TimeChunk.Days]:

Now this is pretty good and kind of improves the redundancy IF I need to use the same strings in multiple switch cases (which I do). So this is cool, but is there a way to make it better?
 
Last edited:
Not directly related to your question: You cannot have more switches after line 33 because it would be unreachable code.
 
Also not directly related to your question, but you should use a more object oriented style/idiom instead of a procedural style/idiom. The C style is like what you have in your original post. It returns some kind of success/failure code, and allocates something in an out parameter. The object oriented style/idiom would look more like:
C#:
string CalculateTime(string units, TimeSpan time);
No success or failure codes. The return value would be the value that is being calculated. If there is an exceptional error, then an exception thrown. For simpler errors, some well known value would be returned instead: null or empty string.
 
I just updated the OP with a subsequent method.
Also not directly related to your question, but you should use a more object oriented style/idiom instead of a procedural style/idiom. The C style is like what you have in your original post. It returns some kind of success/failure code, and allocates something in an out parameter. The object oriented style/idiom would look more like:
C#:
string CalculateTime(string units, TimeSpan time);
No success or failure codes. The return value would be the value that is being calculated. If there is an exceptional error, then an exception thrown. For simpler errors, some well known value would be returned instead: null or empty string.

I naturally don't use "out" parameters much, but in this case there is a reason it needed to be that way. I can't post the whole code, but in the areas where these functions are called it would require a lot more modifications later on. Either that, or exception handling, and I prefer to not use exceptions unless absolutely necessary. I feel it makes my code more bullet proof and portable cause it forces me to pay attention to all the potential holes in the logic and fix them properly. But I'm not against it and in some cases it reduces the number of checks by so much that it's just basically required.
 
I feel it makes my code more bullet proof and portable cause it forces me to pay attention to all the potential holes in the logic and fix them properly.

That what all the C programmers say. I've seen a lot of production C code that never check the success or failure codes. :)

So might as well write C# code like BASIC code with gotos.

Just pushing your buttons. :) I can see how not wanting to churn other code if possible. Of course, that also depends on how risk averse you (or your management) . On the other hand that what unit tests, disciplined refactoring, and source control is supposed to do: lower change risks.
 
The way I would do things...
C#:
static class TimeCalculations
{
    public const double DaysInAWeek = 7.0;
    public const double DaysInAYear = 365.25;
    public const double DaysInAMonth = DaysInAYear / MonthsInAYear;
    public const double MonthsInAYear = 12.0;

    public enum TimeUnit { Unknown, Seconds, Minutes, Hours, Days, Weeks, Months, Years };

    static readonly Dictionary<string, TimeUnit> TimeUnitAbbreviations = new()
    {
        ["secs"] = TimeUnit.Seconds,
        ["mins"] = TimeUnit.Minutes,
    };

    static readonly Dictionary<TimeUnit, double> SecondsPerTimeUnit = new();

    static TimeCalculations()
    {
        SecondsPerTimeUnit[TimeUnit.Unknown] = double.NaN;
        SecondsPerTimeUnit[TimeUnit.Seconds] = 1.0;
        SecondsPerTimeUnit[TimeUnit.Minutes] = SecondsPerTimeUnit[TimeUnit.Seconds] * 60.0;
        SecondsPerTimeUnit[TimeUnit.Hours]   = SecondsPerTimeUnit[TimeUnit.Minutes] * 60.0;
        SecondsPerTimeUnit[TimeUnit.Days]    = SecondsPerTimeUnit[TimeUnit.Hours] * 24.0;
        SecondsPerTimeUnit[TimeUnit.Weeks]   = SecondsPerTimeUnit[TimeUnit.Days] * DaysInAWeek;
        SecondsPerTimeUnit[TimeUnit.Months]  = SecondsPerTimeUnit[TimeUnit.Days] * DaysInAMonth;
        SecondsPerTimeUnit[TimeUnit.Years]   = SecondsPerTimeUnit[TimeUnit.Days] * DaysInAYear;
    }

    public static TimeUnit Parse(string timeUnit)
    {
        if (Enum.TryParse(timeUnit, out TimeUnit result))
            return result;
        timeUnit = timeUnit.ToLower();
        if (TimeUnitAbbreviations.TryGetValue(timeUnit, out TimeUnit value))
            return value;
        return TimeUnit.Unknown;
    }

    public static string GetTimeString(TimeSpan span, string timeUnit)
    {
        var value = Parse(timeUnit) switch
        {
            TimeUnit.Seconds => span.TotalSeconds,
            TimeUnit.Minutes => span.TotalMinutes,
            TimeUnit.Hours   => span.TotalHours,
            TimeUnit.Days    => span.TotalDays,
            TimeUnit.Weeks   => span.TotalDays / DaysInAWeek,
            TimeUnit.Months  => span.TotalDays / DaysInAMonth,
            TimeUnit.Years   => span.TotalDays / DaysInAYear,
            _                => double.NaN,
        };

        if (!double.IsNaN(value))
            return value.ToString();
        return string.Empty;
    }

    public static TimeSpan GetTimeSpan(string number, string timeUnit)
    {
        var units = Parse(timeUnit);
        if (units == TimeUnit.Unknown)
            throw new ArgumentException("Could not parse string a as time unit", nameof(timeUnit));
        if (double.TryParse(number, out double value))
            return TimeSpan.FromSeconds(value * SecondsPerTimeUnit[units]);
        throw new ArgumentException("Could not parse string as a double", nameof(number));
    }

    public static string AddTime(DateTime startDate, string number, string timeUnit)
    {
        try
        {
            return (startDate + GetTimeSpan(number, timeUnit)).ToString();
        }
        catch(ArgumentException)
        {
            return string.Empty;
        }
    }
}
 
Or a better GetTimeString():
C#:
public static string GetTimeString(TimeSpan span, string timeUnit)
{
    var units = Parse(timeUnit);
    if (units == TimeUnit.Unknown)
        return string.Empty;
    return (span.TotalSeconds / SecondsPerTimeUnit[units]).ToString();
}
 
I don't really get your requirement, but you can store methods in variables just like you can data:

You said:
C#:
enum TimeChunk
{
     Days, Hours, Mins, Secs, ...
}

// Then calling would be like:

case commands[TimeChunk.Days]:


If you store methods, you don't even have a switch, you just call the method indexed by the dictionary key:

1689673328044.png


Experiment at:
 

Latest posts

Back
Top Bottom