chevron_left chevron_right
Login Register invert_colors photo_library


Stay updated and chat with others! - Join the Discord!
Thread Rating:
  • 1 Vote(s) - 5 Average


Tutorial C# - Code density saves lives filter_list
Author
Message
C# - Code density saves lives #1
I'm sure we've all seen something like this before....

[Image: simple-java-programs-15-728.jpg?cb=1269419230]

Yes, we all cringed at that. There's a few issues with it, but the most important of which is that there is actually very little code here. Nothing really does anything, it just takes up lines (even with the painful practice of same-line bracing). So, how can we remedy this?

Well, there are a few ways and C# has some neat tricks to help us, I'll cover 3 of these:

  1. reduce declarations
  2. combine if statements
  3. pattern switches

1. Reduce declarations

This one may sound redundant, but C# does have some help here. Modern versions of C# have the ability to declare variables as they are being used, with either the is our out keywords. I'll give you two examples to demonstrate this point (it really is this easy).

Let's first look at this example of some dummy code that would check if a Stripe subscription is active, and if so, do something with it:
Code:
void a(string subscriptionID)
{
    Subscription subscription = null;
    using (Services.Stripe stripeService = new Services.Stripe())
    {
        subscription = stripeService.GetSubscription(subscriptionID);
    }
    if (subscription?.Status == "active")
        ; // do something with active subscription
}
The first thing we can do, is bring that if statement inside of the using, and get rid of the braces there (the if block is treated as a single line). When we bring it in, we don't just want to drop the if right below the assignment line though, that would be pointless. Instead, we can actually move the assignment inside of the if statement, since the variable subscription is already declared. To do this, we just wrap the entire assignment in an extra set of parens and then introduce a check.
Code:
void a(string subscriptionID)
{
    Subscription subscription = null;
    using (Services.Stripe stripeService = new Services.Stripe())
        if ((subscription = stripeService.GetSubscription(subscriptionID)) != null && subscription.Status == "active")
            Console.WriteLine($"{subscription.Id} is active");
}
When moving it in, we can usually check against null if we're planning on using anything within it (I added a writeline to demonstrate). Because we know the first term will either short circuit or be non-null, we can remove the null-conditional check on the Status member. As you can see, doing that alone has reduced the size of our function from 10 lines to 7, but that's not as simple as it can get either..
What if we could get rid of that pesky declaration line up front, and make the code self documenting in the process?
We actually can do this, with the [is] keyword, which only works in our if statements, and acts a lot like how we brought our instantiation inside of our if statement.
To go about it, all we have to do is
  1. Remove the extra parens
  2. Remove everything except of what's to the right of the equals (and get rid of the equals)
  3. Copy the declaration line after the instantiation, separated by is
Code:
void a(string subscriptionID)
{
    using (Services.Stripe stripeService = new Services.Stripe())
        if (stripeService.GetSubscription(subscriptionID) is Subscription subscription && subscription?.Status == "active")
            Console.WriteLine($"{subscription.Id} is active");
}
And of course in this case, I've re-added the null-conditional. This probably isn't required, but I usually add it just in case it might pop out null. Our final function is now 6 lines, a 40% reduction in number of lines over the original, while still functioning exactly the same.

Now let's look at another example. Let's say you have a database, and in a table one of your fields is a datetime, but some pesky database administrator has forced the column to type varchar and made it a non-nullable field (empty string used instead of NULL). This actually happens to me quite a bit with work. Let's also say that your code depends on it being a DateTime? (nullable). We can write up a pretty simple function to convert between the two:
Code:
static DateTime? ParseDateTimeNull(string input)
{
    DateTime parsed;
    if (DateTime.TryParse(input, out parsed))
        return parsed;
    return null;
}
Of course, I left out the braces and a completely useless else statement on this one, I don't think you needed to see it. What we have here is 7 lines of code that's just taking up screen space. What if I told you this could be reduced to just a single line. Well, it can.
Just like with the is keyword, we can modify our call to TryParse to declare the variable parsed for us, and get rid of that declaration.
Code:
static DateTime? ParseDateTimeNull(string input)
{
    if (DateTime.TryParse(input, out DateTime parsed))
        return parsed;
    return null;
}
Ok, now we're down to 6 lines, but we can still get it down further, by making use of the ternary operator:
Code:
static DateTime? ParseDateTimeNull(string input)
{
    return DateTime.TryParse(input, out DateTime parsed) ? parsed : (DateTime?)null;
}
In doing so, we'll need to keep the compiler happy and cast one of the operands on the ternary. I tend to cast the null value to the return type so it's not ambiguous to future developers. Now we're down to 4 lines, it can't get any smaller, can it?
Yes, it can, and I'm not talking about just putting it all on one line. C# actually has a built in operator for  implicit returning. I discussed it briefly in my Time saving shortcuts thread. We simply use => instead of brackets, and drop the return keyword.
Code:
static DateTime? ParseDateTimeNull(string input) => DateTime.TryParse(input, out DateTime parsed) ? parsed : (DateTime?)null;
As you can see, we're now down to just a single line. That's an 86% reduction in what I'd call wasted lines.

2. Combine if statements
This one is pretty self explanatory, I'm sure we've all seen code that's similar to the following:
Code:
else if (user.password != null)
{
    if (!SP.UserEdit.EditPassword(cn, userid, user.password)) return null;
}
This is 4 lines when it ought to only be 2. We don't need to nest this, because there isn't anything else it could do. We can just simply combine these two things, and drop the braces.
Code:
else if (user.password != null && !SP.UserEdit.EditPassword(cn, userid, user.password))
    return null;
You do really need to be careful when doing this though, there are cases where you do NEED to match on that clause.  For example, if there is another else if clause below that, you'll need to add
Code:
&& user.password == null
to that statement in order to retain the functionality of the else if. For most scenarios though, you'll find you have unnecessary nested if statements that can be reduced down to a single statement.
I know this was a short and potentially obvious one, but trust me it needed to be said. I pulled the original example out of a current codebase that I wrote, and I like to think I have an excellent eye for keeping good code density.

3. Pattern switches

This one has been by far the most useful thing for me recently. Let's say you're doing something that switches out the graphic depending on the device OS and the theme. You might have code similar to the following:
Code:
enum DeviceType
{
    Apple,
    Android,
    Blackberry
}
enum Theme
{
    Light,
    Dark,
    Hybrid
}
enum Resource
{
    Settings,
    User
}
string ImagePath(DeviceType device, Theme theme, Resource resource)
{
    switch (resource)
    {
        case Resource.Settings:
            switch (theme)
            {
                case Theme.Dark:
                    switch (device)
                    {
                        case DeviceType.Android: return "dark_settings.png";
                        case DeviceType.Apple: return "Dark/Settings.png";
                        case DeviceType.Blackberry: return "DarkSettings.jpg";
                    }
                    break;
                case Theme.Light:
                    switch (device)
                    {
                        case DeviceType.Android: return "light_settings.png";
                        case DeviceType.Apple: return "Light/Settings.png";
                        case DeviceType.Blackberry: return "LightSettings.jpg";
                    }
                    break;
                case Theme.Hybrid:
                    switch (device)
                    {
                        case DeviceType.Android: return "hybrid_settings.png";
                        case DeviceType.Apple: return "Hybrid/Settings.png";
                        case DeviceType.Blackberry: return "HybridSettings.jpg";
                    }
                    break;
            }
            break;
        case Resource.User:
            switch (theme)
            {
                case Theme.Dark:
                    switch (device)
                    {
                        case DeviceType.Android: return "dark_user.png";
                        case DeviceType.Apple: return "Dark/User.png";
                        case DeviceType.Blackberry: return "DarkUser.jpg";
                    }
                    break;
                case Theme.Light:
                    switch (device)
                    {
                        case DeviceType.Android: return "light_user.png";
                        case DeviceType.Apple: return "Light/User.png";
                        case DeviceType.Blackberry: return "LightUser.jpg";
                    }
                    break;
                case Theme.Hybrid:
                    switch (device)
                    {
                        case DeviceType.Android: return "hybrid_user.png";
                        case DeviceType.Apple: return "Hybrid/User.png";
                        case DeviceType.Blackberry: return "HybridUser.jpg";
                    }
                    break;
            }
            break;
    }
    return null;
}
I don't need to tell anybody that's a lot of code that gets wasted. Only the far inner cases actually do anything. If we ignore the enumeration definitions, we've got 64 lines of code....It doesn't even fit on one screen @1080p with smaller than normal font size. This would get to be an incredibly annoying thing to maintain if this was anything larger than just an example.
Well, C# is again here to the rescue, with pattern switches. This is a new feature that's only available in C#8, but if you can update you should if only for this one feature. Before we get into the big example, let's look at a much smaller example and I'll explain how it works and how to convert them:
Code:
string UserImagePath(DeviceType device)
{
    switch (device)
    {
        case DeviceType.Android: return "dark_user.png";
        case DeviceType.Apple: return "Dark/User.png";
        case DeviceType.Blackberry: return "DarkUser.jpg";
        default: return null;
    }
}
So here, we're only dealing with one switch, but there's a lot going on here that doesn't have to be. It would be nice if we could get rid of some of those braces, and if we could stop typing case and return. Thankfully, there's a new shorthand for switches like this. We can simply reverse the order of that first line, get rid of case and return, replace the semocolons with comas, and replace the colons with =>. The result is the following:
Code:
string UserImagePath(DeviceType device)
{
    return device switch
    {
        DeviceType.Android => "dark_user.png",
        DeviceType.Apple => "Dark/User.png",
        DeviceType.Blackberry => "DarkUser.jpg",
        _ => null
    };
}
As you can see, it's a whole lot easier to read. I should mention that underscore is the equivalent of the default keyword. But, we still haven't gotten rid of any of the braces. Thankfully, since that switch is now a single statement, we can drop the return and method braces.
Code:
string UserImagePath(DeviceType device) => device switch
{
    DeviceType.Android => "dark_user.png",
    DeviceType.Apple => "Dark/User.png",
    DeviceType.Blackberry => "DarkUser.jpg",
    _ => null
};
We're now down to 7 lines, from 10, a 30% density improvement. Let's go ahead and convert our original example using these:
Code:
string ImagePath(DeviceType device, Theme theme, Resource resource)
{
    switch (resource)
    {
        case Resource.Settings:
            switch (theme)
            {
                case Theme.Dark: return device switch
                {
                    DeviceType.Android => "dark_settings.png",
                    DeviceType.Apple => "Dark/Settings.png",
                    DeviceType.Blackberry => "DarkSettings.jpg",
                    _ => null
                };
                case Theme.Light: return device switch
                {
                    DeviceType.Android => "light_settings.png",
                    DeviceType.Apple => "Light/Settings.png",
                    DeviceType.Blackberry => "LightSettings.jpg",
                    _ => null
                };
                case Theme.Hybrid: return device switch
                {
                    DeviceType.Android => "hybrid_settings.png",
                    DeviceType.Apple => "Hybrid/Settings.png",
                    DeviceType.Blackberry => "HybridSettings.jpg",
                    _ => null
                };
            }
            break;
        case Resource.User:
            switch (theme)
            {
                case Theme.Dark: return device switch
                {
                    DeviceType.Android => "dark_user.png",
                    DeviceType.Apple => "Dark/User.png",
                    DeviceType.Blackberry => "DarkUser.jpg",
                    _ => null
                };
                case Theme.Light: return device switch
                {
                    DeviceType.Android => "light_user.png",
                    DeviceType.Apple => "Light/User.png",
                    DeviceType.Blackberry => "LightUser.jpg",
                    _ => null
                };
                case Theme.Hybrid: return device switch
                {
                    DeviceType.Android => "hybrid_user.png",
                    DeviceType.Apple => "Hybrid/User.png",
                    DeviceType.Blackberry => "HybridUser.jpg",
                    _ => null
                };
            }
            break;
    }
    return null;
}
We're now down to 58 lines (9% density improvement). The entire function now fits on my screen, but it's still got a lot of jumbo in it. We could eliminate the breaks by simply converting the next level up to pattern switches as well:
Code:
string ImagePath(DeviceType device, Theme theme, Resource resource)
{
    switch (resource)
    {
        case Resource.Settings: return theme switch
            {
                Theme.Dark => device switch
                {
                    DeviceType.Android => "dark_settings.png",
                    DeviceType.Apple => "Dark/Settings.png",
                    DeviceType.Blackberry => "DarkSettings.jpg",
                    _ => null
                },
                Theme.Light => device switch
                {
                    DeviceType.Android => "light_settings.png",
                    DeviceType.Apple => "Light/Settings.png",
                    DeviceType.Blackberry => "LightSettings.jpg",
                    _ => null
                },
                Theme.Hybrid => device switch
                {
                    DeviceType.Android => "hybrid_settings.png",
                    DeviceType.Apple => "Hybrid/Settings.png",
                    DeviceType.Blackberry => "HybridSettings.jpg",
                    _ => null
                },
                _ => null
            };
        case Resource.User: return theme switch
            {
                Theme.Dark => device switch
                {
                    DeviceType.Android => "dark_user.png",
                    DeviceType.Apple => "Dark/User.png",
                    DeviceType.Blackberry => "DarkUser.jpg",
                    _ => null
                },
                Theme.Light => device switch
                {
                    DeviceType.Android => "light_user.png",
                    DeviceType.Apple => "Light/User.png",
                    DeviceType.Blackberry => "LightUser.jpg",
                    _ => null
                },
                Theme.Hybrid => device switch
                {
                    DeviceType.Android => "hybrid_user.png",
                    DeviceType.Apple => "Hybrid/User.png",
                    DeviceType.Blackberry => "HybridUser.jpg",
                    _ => null
                },
                _ => null
            };
    }
    return null;
}
Ok, now we're down to 54 lines (15% density improvement). But, what if we went another level up, could we reduce it any further?
Code:
string ImagePath(DeviceType device, Theme theme, Resource resource) => resource switch
{
    Resource.Settings => theme switch
    {
        Theme.Dark => device switch
        {
            DeviceType.Android => "dark_settings.png",
            DeviceType.Apple => "Dark/Settings.png",
            DeviceType.Blackberry => "DarkSettings.jpg",
            _ => null
        },
        Theme.Light => device switch
        {
            DeviceType.Android => "light_settings.png",
            DeviceType.Apple => "Light/Settings.png",
            DeviceType.Blackberry => "LightSettings.jpg",
            _ => null
        },
        Theme.Hybrid => device switch
        {
            DeviceType.Android => "hybrid_settings.png",
            DeviceType.Apple => "Hybrid/Settings.png",
            DeviceType.Blackberry => "HybridSettings.jpg",
            _ => null
        },
        _ => null
    },
    Resource.User => theme switch
    {
        Theme.Dark => device switch
        {
            DeviceType.Android => "dark_user.png",
            DeviceType.Apple => "Dark/User.png",
            DeviceType.Blackberry => "DarkUser.jpg",
            _ => null
        },
        Theme.Light => device switch
        {
            DeviceType.Android => "light_user.png",
            DeviceType.Apple => "Light/User.png",
            DeviceType.Blackberry => "LightUser.jpg",
            _ => null
        },
        Theme.Hybrid => device switch
        {
            DeviceType.Android => "hybrid_user.png",
            DeviceType.Apple => "Hybrid/User.png",
            DeviceType.Blackberry => "HybridUser.jpg",
            _ => null
        },
        _ => null
    },
    _ => null
};
Yep, we shaved another line off of it. We now have a 17% increase in density. We're not done yet though. We have a lot of duplicated default cases, as well as way too many sets of braces. Well it turns out that we don't have to nest our switch statements, we can combine them all into a single one. We do this simply by creating a tuple out of the whole set. We can just wrap multiple condition layers into a single case by putting parens around the combination of selectors. Rather than describe it more, I'll just show you the final result.
Code:
string ImagePath(DeviceType device, Theme theme, Resource resource) => (resource, theme, device) switch
{
    (Resource.Settings, Theme.Dark, DeviceType.Android) => "dark_settings.png",
    (Resource.Settings, Theme.Dark, DeviceType.Apple) => "Dark/Settings.png",
    (Resource.Settings, Theme.Dark, DeviceType.Blackberry) => "DarkSettings.jpg",
    (Resource.Settings, Theme.Light, DeviceType.Android) => "light_settings.png",
    (Resource.Settings, Theme.Light, DeviceType.Apple) => "Light/Settings.png",
    (Resource.Settings, Theme.Light, DeviceType.Blackberry) => "LightSettings.jpg",
    (Resource.Settings, Theme.Hybrid, DeviceType.Android) => "hybrid_settings.png",
    (Resource.Settings, Theme.Hybrid, DeviceType.Apple) => "Hybrid/Settings.png",
    (Resource.Settings, Theme.Hybrid, DeviceType.Blackberry) => "HybridSettings.jpg",
    (Resource.User, Theme.Dark, DeviceType.Android) => "dark_user.png",
    (Resource.User, Theme.Dark, DeviceType.Apple) => "Dark/User.png",
    (Resource.User, Theme.Dark, DeviceType.Blackberry) => "DarkUser.jpg",
    (Resource.User, Theme.Light, DeviceType.Android) => "light_user.png",
    (Resource.User, Theme.Light, DeviceType.Apple) => "Light/User.png",
    (Resource.User, Theme.Light, DeviceType.Blackberry) => "LightUser.jpg",
    (Resource.User, Theme.Hybrid, DeviceType.Android) => "hybrid_user.png",
    (Resource.User, Theme.Hybrid, DeviceType.Apple) => "Hybrid/User.png",
    (Resource.User, Theme.Hybrid, DeviceType.Blackberry) => "HybridUser.jpg",
    (_, _, _) => null
};
The way this works, is it will go through each one and as soon as it matches one it will return its value. We simply cover all cases and at the end we include an all wildcard that returns null.
Our final line count is 21 lines (67% density improvement). There is no limit to how many patterns you use, and you can place the wildcards in any position within the selectors or case list, allowing you to make very specific or general case statements that match a variety of parameters without having to nest your switch statements and with minimal code that doesn't do anything.

To demonstrate this, we can even make this smaller since this follows a format (I did it this way to demonstrate the uses, but this is a simple example).
Code:
string ImagePath(DeviceType device, Theme theme, Resource resource) => (device) switch
{
    DeviceType.Android => $"{theme.ToString().ToLower()}_{resource.ToString().ToLower()}.png",
    DeviceType.Apple => $"{theme.ToString()}/{resource.ToString()}.png",
    DeviceType.Blackberry => $"{theme.ToString()}{resource.ToString()}.jpg",
    _ => null
};
I did this to demonstrate that you may not always need to be so specific with your statements. You can see that everything follows a specific format, and so we can just program our switch to use that format. This one is only 6 lines long (91% density improvement). Make sure you look for switches that can be simplified in this way. Sometimes converting it to a multi-parameter pattern switch helps you identify what the pattern is so you can simplify like this. Don't be afraid to do it.



Well, I hope you guys enjoyed this, please feel free to leave your comments, sample code you'd like to see neat ways to simplify/reduce, or other topic ideas below.

[+] 1 user Likes phyrrus9's post
Reply

RE: C# - Code density saves lives #2
It's nice to see you're still making tutorial threads like these.

I do feel like C++ is getting left behind now, only because it's an older language. I don't see a lot of it anymore. So I'm transitioning to other ones now. C# would probably be a better alternative for me.
[Image: tumblr_n4fsswcwZa1sbhzgao1_250.gif]

"Crack it open, throw it in a pan and let it cook." ~ Filthy Frank

Reply

RE: C# - Code density saves lives #3
(11-05-2019, 06:03 PM)Drako Wrote: It's nice to see you're still making tutorial threads like these.

I do feel like C++ is getting left behind now, only because it's an older language. I don't see a lot of it anymore. So I'm transitioning to other ones now. C# would probably be a better alternative for me.

I've never been a big C++ fan myself, at heart I'm a C guy, but for the majority of the commercial software I write C just isn't worth the budget. My clients are willing to take the performance and security hits to use C# instead of paying me likely 3 times as much to do it in C.

Reply






Users browsing this thread: 1 Guest(s)