Article

Save time with C#

June 02, 2017  |  Code School
Learn something new. Take control of your career.
Sign up

In our new Keeping It Classy With C# course, we cover the basics of C# for those building their first C# apps. Once you play through it and get hands-on experience with this versatile language, it's time to start exploring its many helpful features—including some that could save you a lot of trouble in the long run. Here, we'll take you through a few examples of how to make C# truly work for you (and a few illustrations of what not to do). 

Optional parameters

One common use for method overloads are to handle cases where you have parameters you don’t require. Consider the following method overloads:

AddUser(string lastName)
AddUser(string firstName, string lastName)
AddUser(string firstName, string middleName, string lastName)

Ultimately, all three of these methods create the User object using the provided strings. While method overloads are a reasonable choice for this, there’s a more readable way to do this.

AddUser(string lastName, string firstName = string.Empty, string middleName = string.Empty)

In this example, we use optional parameters where, in the event a parameter is not provided, we provide it a default value. This lets us consolidate all of these method overloads into a single clean method!

Null conditional operators

Sometimes handling null values can become a real pain, creating large and annoying conditions to make sure everything is handled properly. In the following code, we’ve connected to a remote database and are trying to manipulate some records in a table—but we need to do a lot of null checks to avoid throwing errors.

if(dbConnection != null && dbConnection.Table != null && dbConnection.Table.Records != null)
{
  Return dbConnection.Table.Records.Count;
}

That’s a pretty ugly condition. Now imagine I’m comparing two or more separate bits of information to create that condition.

if(dbConnection != null && dbConnection.Table1 != null && dbConnection.Table2 != null && dbConnection.Table3 != null && dbConnection.Table1.Records != null && dbConnection.Table2.Records != null && dbConnection.Table3.Records != null && dbConnection.Table1.Records.Count == dbConnection.Table2.Records.Count && dbConnection.Table1.Records.Count > dbConnection.Table3.Records.Count)
{
  Return dbConnection.Table1.Records.Count - dbConnection.Table3.Records.Count;
}

These are the sort of nightmare conditional blocks you want to avoid, because in the end all we really care about is if Table1 has the same number of records as Table2, and if that was more records than Table3.

Luckily, we can make this way nicer using null conditional operators, which let us do our null checks in a nice shorthand manner.

if(dbConnection?.Table1?.Records?)
{
  Return dbConnection.Table.Records.Count;
}

Now let’s see our extreme condition cleaned up using null conditional operators.

if(dbConnection?.Table1?.Records?.Count == dbConnection?.Table2?.Records?.Count && dbConnection?.Table1?.Records?.Count > dbConnection?.Table3?.Records?.Count)
{
  Return dbConnection.Table1.Records.Count - dbConnection.Table3.Records.Count;
}

Sure, it’s still not “pretty,” but it’s a lot easier to read than it was without using null conditional operators.

Try/catch exceptions

While it’s typically best to handle errors before they happen, there are cases where it’s appropriate to throw an exception or prevent a thrown exception from reaching a user.

Consider the following scenario. Our application is accessing a third-party webservice. At any time, that service could go offline, respond in an unexpected way, etc. While we write our code expecting it to work a certain way, we do need to cover our bases in the event it behaves unexpectedly.

We have several choices on how we could handle this:

Let the user see the error

Var results = (BoardGame)WebService.ReadToEnd();

When the webservice misbehaves, our users are faced with an error message. If you’re lucky, something logged the error, but there’s a good chance nothing did, so you wouldn’t even be aware something went wrong until someone complains. Not a great way of doing things.

Catch and swallow errors

Try
{
  Var results = (BoardGame)WebService.ReadToEnd();
}
Catch(Exception ex)
{
  // do nothing
}

This is arguably even worse than throwing errors at the user. In this case, something went wrong, but to the user it seems everything is going well! This can cause all sorts of problems, like making the user think they successfully paid for an order. Generally, there is no worse form of error handling than just making errors “disappear.”

Handle errors

Try
{
  Var results = (BoardGame)WebService.ReadToEnd();
}
Catch(Exception ex)
{
  Logger.Log(ex);
  DisplayUserFriendlyErrorMessage();
}

OK, getting better. For a lot of situations, the standard practice is to provide the user with a message like, “Something went wrong—please try again later” and then log the error for later review. If the error is bad enough, log it and immediately notify whomever is responsible for the application so they can address it ASAP.

Handle specific errors

Try
{
  Var results = (BoardGame)WebService.ReadToEnd();
}
Catch(TimeOutException ex)
{
  Logger.Log(ex, Severity.Medium);
  Alert(Staff.IT);
  DisplayCachedDataToUser();
}
Catch(InvalidCastException ex)
{
  Logger.Log(ex, Severity.Critical);
  Alert(Staff.Developers);
  DisplayErrorToUser();
}
Catch(Exception ex, Severity.High)
{
  Logger.Log(ex);
  Alert(Staff.Everyone);
  DisplayErrorToUser();
}

Our final example is a better way of handling things. In all cases, we log the error and provide a severity, alert the appropriate staff and provide some kind of feedback to the user.

If we get a TimeOutException, it means the webservice took too long to respond, so we log it, alert IT we might have network issues with error details and display the data from the last time they called the service.

If we get an InvalidCastException, it could be serious. This means that the data the webservice provided us isn’t what we expected—thus breaking our application. In this case, our application is broken until we can adapt to the change, meaning we might need developers to respond immediately.

Finally, if we still get an error, but it’s not one we’ve planned for, we log the error, alert everyone (since we’re not sure who needs to address it) and provide a “Try again at a later date”-style error to the user.

In the end, the important thing is to never bury or hide an error. At the very least, you'll want to log the error somewhere. Even better: Predict and handle errors so that they get the attention of those who need to address the problem—as well as details, so they can respond in the best way possible.

These are just a few of the countless features available in C#. You’ll find the more you look into a language’s features and know what functionality is available, the easier it is to write clean, efficient and performant code (and hopefully run into as few errors as possible). Play our Keeping It Classy With C# course to learn even more and get first hand experience building a C# app.

Learn something new. Take control of your career.
Sign up

Code School

Code School is an on-demand learning destination for existing and aspiring developers. Each course is built around a creative theme and... See more