I sometimes run into situations where, if a line of code throws an exception, I don't care what the exception is or why it was thrown; I just need to take a generic action and move on. For example:
try
{
// Throws IndexOutOfRangeException when DB field
// is null in the current record
Template = record.GetString(MYSQL_IDX_TEMPLATE);
}
catch
{
Template = FIELD_UNAVAILABLE;
}
I was wondering if there's a short-hand way to set a value based on a generic try/catch?
I'm thinking of something similar to ternary syntax:
string blah = (index >= ubound ? "No more records" : "records exist");
Related
Sorry if this is a simple question; this is my first language and I'm trying my best to seek out and follow examples and explanations on this site and otherwise.
I've been trying to expand on a Microsoft C# tutorial program that creates "bank accounts." I'm trying to work on catching and handling exceptions, specifically by prompting the user to try again for a valid input.
I've come across this thread and many similar threads about running a loop while the input is invalid, and this example specifically using try/catch, which if I'm understanding correctly, is what I want to use here because I have a few lines of code that could throw multiple exceptions (it could be non-numerical or it could be negative). Following those and other examples, I can't figure out how to assign the initial balance input to a value that I can reference outside the loop (but still only within the CreateAccount method) once the input is valid.
I'm not sure what I have currently is working otherwise, but currently this code produces an error because initBalInput is left unassigned after the while loop, even though it's declared outside the loop and assigned in the try block.
public static void CreateAccount()
{
// Prompt for BankAccount constructor parameter {name} which is passed to BankAccount.Owner in constructor
Console.WriteLine("Name on new account: ");
string nameInput = Console.ReadLine();
decimal initBalInput;
bool valid = false;
while (valid == false)
{
try
{
Console.WriteLine("How much to deposit for initial balance: ");
initBalInput = Convert.ToDecimal(Console.ReadLine());
}
catch (ArgumentOutOfRangeException)
{
Console.WriteLine("Initial balance must be positive!");
valid = false;
continue;
}
catch (FormatException)
{
Console.WriteLine("Initial balance must be a number!");
valid = false;
continue;
}
valid = true;
}
// Create new instance "account" of type BankAccount and set its parameters
BankAccount account = new BankAccount(nameInput, initBalInput);
Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");
}
Instead of catching the exceptions, write the code that handles the invalid input.
public static void CreateAccount()
{
// Prompt for BankAccount constructor parameter {name} which is passed to BankAccount.Owner in constructor
Console.WriteLine("Name on new account: ");
string nameInput = Console.ReadLine();
string initBalInput = Console.ReadLine();
// try parse will check for invalid decimal values and also, positive values can be checked
if(decimal.TryParse(initBalInput, out decimal initBal) && initBal > 0) {
// Create new instance "account" of type BankAccount and set its parameters
BankAccount account = new BankAccount(nameInput, initBal);
Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");
} else {
Console.WriteLine("Invalid initial balance");
}
}
but currently this code produces an error because initBalInput is left unassigned after the while loop, even though it's declared outside the loop and assigned in the try block
The problem is that the compiler doesn't know if execution will ever reach the try block:
while (valid == false)
is evaluated at runtime. You and me both know that execution will enter at least once the while loop because valid is initially false but the compiler doesn't go into that type of analysis where variables are involved and therefore assumes execution might never enter the while loop and an unitialized initBalInput can be read.
That said, you should not get into the habit of using exepctions as control flow mechanisms. Exceptions should be exceptions, don't base the logic of your programs around exceptions. In your case, you should look into the method decimal.TryParse.
Also, always break up your problem into smaller problems. At the beginning, start small, make one liner methods that are obviously correct. It's very hard to write a bug in methods that are one or two lines long.
So what do you need?
A method that prompts the user for an input.
A method that validates the input
Something that asks the user to try again if the input is wrong.
Ok, numer one:
static string RequestUserInput(string message)
{
Console.Write(message);
return Console.ReadLine();
}
Number two: We already have it with decimal.TryParse(string, out decimal d). This method will return true if the input string can be parsed into a valid decimal number which will be assigned to d and false otherwise.
Number three:
public static decimal GetDecimalInput(string message)
{
decimal d;
while (true)
{
if (!decimal.TryParse(RequestUserInput(message), out d))
//tryparse failed, input is not a valid decimal number
Console.WriteLine("Initial balance must be a number!");
else if (d < 0) //try parse succeeded, we know input is a valid
// decimal number d but it might be negative.
Console.WriteLine("Initial balance must be positive!");
else
//we know inout is a valid non negative decimal number.
//break out of the loop, we don't need to ask again.
break;
}
return d;
}
And now, you put it all together:
var accountBalance = GetDecimalInput("How much to deposit for initial balance: ");
First, I have two articles on Exception handling that I consider required reading:
This one helps to classify the 4 common exception types - and if you should even consider catching them.
While this one goes into more details for good practices.
You should not be using convert, but parse. Or even better TryParse(). The exceptions on the string -> number conversion are the examples for vexing exceptions.
If there is no TryParse, I did once wrote a custom implementation of Int.TryParse() for someone still on Framework 1.1:
//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).
bool TryParse(string input, out int output){
try{
output = int.Parse(input);
}
catch (Exception ex){
if(ex is ArgumentNullException ||
ex is FormatException ||
ex is OverflowException){
//these are the exceptions I am looking for. I will do my thing.
output = 0;
return false;
}
else{
//Not the exceptions I expect. Best to just let them go on their way.
throw;
}
}
//I am pretty sure the Exception replaces the return value in exception case.
//So this one will only be returned without any Exceptions, expected or unexpected
return true;
}
But that code looks like you want to have detailed information why it failed. At wich point you may have to write a detailed list of catch blocks.
So, I've been having a little bit of an argument with a fellow coder lately. Specifically: We've been arguing about the proper use of Try / Catch and If / Else as well as First() / Single() in a specific scenario.
Variation #1 - His approach
var items = Search(dto.number); // Returns a List of Objects
if (items.Count = 1)
{
wPerson = items.First();
}
else
{
return;
}
Variation #2 - I changed his code to this.
var items = Search(dto.number); // Returns a List of Objects
try
{
wPerson = items.Single();
}
catch
{
// Handle exception
return;
}
We expect the result of var items = Search(dto.number); to always be 1.
The Question:
Which changes were nescessary? I am trying to defend my point of view below. Please correct me.
First of all: The Single(). I always try to be as specific as possible and I decided on the following rules:
First --> Accepts N
FirstOrDefault --> Accepts 0 and N
Single --> Accepts 1
SingleOrDefault --> Accepts 0 and 1
First() may have worked but it wasn't 100% specific, so I considered it wrong since we had an option to be even more specific.
Secondly: Try / Catch vs If / Else. Since I had already changed First() to Single(), I considered the if statement to be redundant. Yes. I know that Try / Catch is less performant than the If statement but we expect to have NOTHING BUT 1 result. And if we get more or less, I expect it to be a mistake and with Try / Catch I'd actually treat it that way.
Am I so off here?
Well, throwing / catching exceptions is for exceptional situation only, so your current code
try {
wPerson = items.Single();
}
catch {
// Something is very wrong (e.g. RDBMS table's ruined, file's corrupted etc)
throw;
}
means that you expect one and only one wPerson in items. If, however, 0, 1, or 2 item in items is expected behaviour
you should not use Single, but, say, Take
var items = Search(dto.number); // Returns a List of Objects
var wPersons = items.Take(2).ToArray();
if (wPersons.Length <= 0) {
// No person found; probably, wPerson = some_default_value
...
}
else if (wPersons.Length == 1) {
// Exactly one person found
wPerson = wPersons[0];
...
}
else {
// Two or more persons found; we have to resolve tie somehow
...
}
Don't try/catch if you can avoid it. An exception should be used to "exceptional flow", iE: not to be expected.
However I'd suggest you combine both of the variations.
var items = Search(dto.number); // Returns a List of Objects
if (items.Count != 1) return;
wPerson = items.Single();
Since you expect only 1 result if successful, you can immediately break.
If, for any reason, you change your code and there are x > 1 items this method will break since you only expect 1 (hence the Single) and not go into an "unexceptional" flow (assuming the First object is the correct object)
I'm constantly getting the same error.
An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Windows.Forms.dll
Additional information: InvalidArgument=Value of '0' is not valid for 'index'.
The Code is:
private void radGridView1_CellFormatting(object sender, Telerik.WinControls.UI.CellFormattingEventArgs e)
{
if (e.CellElement.ColumnInfo.HeaderText == "logo")
{
if (e.CellElement.RowInfo.Cells[2].Value.ToString() == "Error")
{
e.CellElement.Image = (Image)imageList1.Images[0];
e.CellElement.ToolTipText = "Error";
}
else if (e.CellElement.RowInfo.Cells[2].Value.ToString() == "Warning")
{
e.CellElement.Image = imageList1.Images[1];
e.CellElement.ToolTipText = "Warning";
}
else if (e.CellElement.RowInfo.Cells[2].Value.ToString() == "Message")
{
e.CellElement.Image = imageList1.Images[2];
e.CellElement.ToolTipText = "Message";
}
}
}
}
//-----------------------------------------------------------------------
MSDN says this
The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.
about the ArgumentOutOfRangeException. It goes on to say that
You are retrieving the member of a collection by its index number, and the index number is invalid.
This is the most common cause of an ArgumentOutOfRangeException
exception. Typically, the index number is invalid for one of three
reasons:
The collection has no members, and your code assumes that it does.
You're attempting to retrieve an item whose index is negative. This
usually occurs because you've searched a collection for the index of a
particular element and have erroneously assumed that the search is
successful.
You're attempting to retrieve an element whose index is equal to the
value of the collection's Count property.
My suspicion is that it's the first case here, i.e. imageList1 is empty. So your imageList1.Images[0] throws the exception because there is nothing there.
To determine if this is the case try imageList1.Images.Count. Given you're looking at 3 elements in your code Count must be >= 3.
From the looks of it, you don't have anything in the array. Do a check to see if the indices are actually initialized, and then go on from there.
In a pre-sorted List<int> I am about to find the last element that satisfies the condition such as int lastScore = list.Last(x => x < 100). If there is no element(s) in the list that satisfies this condition, an InvalidOperationException is thrown with the error message: Sequence contains no matching element. This happens with list.First(...) too.
I even tried to make lastScore nullable to no avail.
Is catching the exception and manually assigning lastScore to null the only way out?
Use FirstOrDefault or LastOrDefault to get null if there is no match, assuming you are working with reference types. These methods will return the default value for value types.
I would probably just catch the Exception at the closest-point of use.
This is because LastOrDefault/FirstOrDefault over an IEnumerable<int> will return 0 (the default for an int), which may be a "valid" value - this depends on the actual context and defined rules. While converting the sequence to an IEnumerable<int?> would allow the previous methods to return null, that seems like more work than it is worth.
If needing to use lastScore on a continued use, consider:
int? lastScore; /* Using a Nullable<int> to be able to detect "not found" */
try {
lastScore = list.Last(x => x < 100); /* int -> int? OK */
} catch (InvalidOperationException) {
lastScore = null; /* If 0 see LastOrDefault as suggested;
otherwise react appropriately with a sentinel/flag/etc */
}
if (lastScore.HasValue) {
/* Found value meeting conditions */
}
Or if able to discard the case when it isn't found, consider:
try {
var lastScore = list.Last(x => x < 100);
/* Do something small/immediate that won't throw
an InvalidOperationException, or wrap it in it's own catch */
return lastScore * bonus;
} catch (InvalidOperationException) {
/* Do something else entirely, lastScore is never available */
return -1;
}
Bit of a coding newbie here and looking for some advice!
I have the following method - a simple weight converter for kilograms to pounds, that's part of a larger console application. The user selects what conversion scheme they want and then they enter their weight and it will convert it. I was doing some unit tests for it to get a better understanding of them and decided to add some code that will return an exception if the user enters a minus number, just to make it more robust. The code is as follows:
public static double KilogramsToPounds(string weightInKilos)
{
//Convert parameter to a double for calculation
double kiloWeight = Double.Parse(weightInKilos);
//Convert Kilograms to Pounds
double poundWeight = kiloWeight * 2.20462;
try
{
if (kiloWeight < 0)
{
throw new ArgumentOutOfRangeException();
}
else
{
return poundWeight;
}
}
catch (ArgumentOutOfRangeException argEx)
{
Console.WriteLine(argEx);
}
return 0;
}
However, when this runs it will always return 0 because it requires a double as its return type. So you get the ArgumentOutOfRange Exception, AND a 0 because it requires a double.
I was just wondering if there was any way to basically say "if you enter a number below 0, you get this error message, but if you enter a valid positive number, you will get the correct numerical result"? Because it seems no matter what you will need to provide a numerical value for it to be happy as well as the error message, or else you will get a "Not all code paths return a value" error.
Any help is greatly appreciated.
Note - I should add the string parameter "weightInKilos" is taken from the user's console input. The code to show the choices is in a different file, and the conversion rates are in a separate file.
Remove the try-catch from your method. Just throw your exception. Your method is not supposed to catch exceptions it throws itself.
Put the try-catch around the call to KilogramsToPounds.
The key to this is that throwing an exception will stop execution of the function, thus getting you around the whole "Not all code paths return a value" error. For instance, this is valid:
public int ZeroOrError(bool error)
{
if (error)
throw new ArgumentOutOfRangeException();
else
return 0;
}
even though technically you don't return a value in the first part of the if block.
So you just need to not catch your exception (which you shouldn't be doing anyway) and you should be good to go.