![]() |
|
Tutorial C# - Code density saves lives - Printable Version +- Sinisterly (https://sinister.li) +-- Forum: Coding (https://sinister.li/Forum-Coding) +--- Forum: Visual Basic & .NET Framework (https://sinister.li/Forum-Visual-Basic-NET-Framework) +--- Thread: Tutorial C# - Code density saves lives (/Thread-Tutorial-C-Code-density-saves-lives) |
C# - Code density saves lives - phyrrus9 - 11-05-2019 I'm sure we've all seen something like this before.... ![]() 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 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
}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");
}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
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");
}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;
}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;
}Code: static DateTime? ParseDateTimeNull(string input)
{
return DateTime.TryParse(input, out DateTime parsed) ? parsed : (DateTime?)null;
}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;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;
}Code: else if (user.password != null && !SP.UserEdit.EditPassword(cn, userid, user.password))
return null;Code: && user.password == nullI 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;
}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;
}
}Code: string UserImagePath(DeviceType device)
{
return device switch
{
DeviceType.Android => "dark_user.png",
DeviceType.Apple => "Dark/User.png",
DeviceType.Blackberry => "DarkUser.jpg",
_ => null
};
}Code: string UserImagePath(DeviceType device) => device switch
{
DeviceType.Android => "dark_user.png",
DeviceType.Apple => "Dark/User.png",
DeviceType.Blackberry => "DarkUser.jpg",
_ => null
};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;
}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;
}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
};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
};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
};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. RE: C# - Code density saves lives - Drako - 11-05-2019 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. RE: C# - Code density saves lives - phyrrus9 - 11-05-2019 (11-05-2019, 06:03 PM)Drako Wrote: It's nice to see you're still making tutorial threads like these. 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. |