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)
Related
A few days ago I asked a question on this site, but I think the people who shared their time to help me (thank you to them) did not really realize my point of view. Here is the link Catch and Continue? C#
They thought I wanted to end the try-catch, and continue with the rest of the code. But I don't
That is my question but more reformed:
I want to get a try-catch, but I need the try to the end even it returns an exception. like this:
// I thought a perfect example with math for this case.
// It is possible to divide a number with negative and positive numbers
// but it is not possible to divide a number by zero, So
// 5/5= 1 // ok
// 5/4= 1.25 // ok
// 5/3= 1.66666666667 // ok
// 5/2= 2.5 // ok
// 5/1= 5 // ok
// 5/0= Math Error // Oh, this is an error so I stop the try here.
// 5/-1= -5 // foo
// 5/-2= -2.5 // foo
// 5/-3= -1.66666666667 // foo
// 5/-4= -1.25 // foo
// 5/-5= -1 // foo
// foo = This is not a error, but I will not do it because the previous error
What I need here is to "ignore" that exception and continue try-catch (divide all positive and negative numbers by ignoring division by zero.) How can I do it?
This is just a clear example for my problem, I know someone would say to put all the "numbers" in a listbox and remove what I do not want however my original code always returns the same exception
With undefined results (both can be x as y can be).
(Is not a critical exception such as lack of memory or capacity, is a simple exception that will not make any logical problem, so there is no problem ignoring the exception)
Thank You!
I think this is what you want:
foreach(var x = 5; x > -5; x--)
{
try
{
// Do the math here
}
catch(Exception)
{
// Log/Print exception, just don't throw one or the loop will exit
}
}
The code above will continue processing even if an exception occurs.
try { operation1(); } catch { }
try { operation2(); } catch { }
try { operation3(); } catch { }
...
As a side note, if you find yourself wanting to do this, chances are your design pattern is flawed.
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;
}
I love LINQ statements for the expressive syntax and other convenient features. However, I find it very troublesome to debug them sometimes. Specifically, when I run a LINQ statement on a collection and one of the elements in the collection causes an exception, how can I figure out what the problem input was and where the problem came from?
Imagine I have a text file with 1000 real numbers:
0.46578
12.314213
1.444876
...
I am reading this as a List<string> and loading it into a more specific data structure:
var file_contents = File.ReadAllLines("myfile.txt");
var data = file_contents.Select(s => double.Parse(s));
Now, for this particular input, I didn't bother to look at it carefully and it turns out the 876th line contains (line numbers shown):
875 5.56786450
876 Error: Could not calculate value.
878 0.0316213
For whatever reason (perhaps the file was generated by a script that malfunctioned). My LINQ method chain will of course throw an exception. The problem is, how do I figure which element of the list caused the exception, and what its value was?
To clarify, if instead I used a for-loop:
var data = new List<double>();
foreach(var row in file_contents)
{
var d = double.Parse(row);
data.Add(d);
}
Then the exception would highlight the string which calls double.Parse, and I would be able to mouse over row to easily see what the problem input was.
I can, of course, use Resharper to convert my LINQ statements into for-loops, and then debug them, but is there a better way?
Put a conditional breakpoint on the lambda function, where the condition is s.StartsWith("5.56"). You just need to have your cursor on the lambda and press F9. Assuming you're using visual studio.
var data = file_contents.Select(s => {
try
{
return double.Parse(s);
}
catch
{
throw; //breakpoint?
}
});
Disclaimer: I work for OzCode
LINQ debugging is hard borderline impossible using Visual Studio. I suggest you try using OzCode.
This is what your code looks when debugging (the exception in on the 6th item).
You can tell which item caused the exception by investigating the items that where passed to the Select clause - and since the last one triggered the exception - it's easy to find the offending value.
If you're interested you can try OzCode's LINQ debugging - we've just started an EAP
I would just use a tryparse personally.
var data = new List<string>
{
"0.46578",
"12.314213",
"Error: Could not calculate value.",
"1.444876",
};
double d;
var good = data.Where(s => Double.TryParse(s, out d)).Select(Double.Parse);
var bad = data.Where(s => !Double.TryParse(s, out d)).Select(x => new
{
key = data.IndexOf(x),
value = x
}).ToDictionary(x => x.key, x => x.value);
textBox1.AppendTextAddNewLine("Good Data:");
WriteDataToTextBox(good);
textBox1.AppendTextAddNewLine(String.Format("{0}{0}Bad Data:", Environment.NewLine));
WriteDataToTextBox(bad);
The AppendTextAddNewLine is simply an extension method I wrote for my little proof of concept test program
public static void AppendTextAddNewLine(this TextBox textBox, string textToAppend)
{
textBox.AppendText(textToAppend + Environment.NewLine);
}
Edit
The WriteDataToTextbox is a generic method that writes an IEnumerble<T> out to the text box.
void WriteDataToTextBox<T>(IEnumerable<T> data )
{
foreach (var row in data)
{
textBox1.AppendTextAddNewLine(row.ToString());
}
}
Forgot to put the output here so I figure I should do that. It shows the index of the bad data and the data itself that caused the problem.
Good Data:
0.46578
12.314213
1.444876
Bad Data:
[2, Error: Could not calculate value.]
I'm not sure why you don't like foreach loop here. LINQ uses it internally anyway, and as you've already realized there are some pros and cons of using LINQ and debugging is one of cons.
I would probably mix LINQ with foreach and end up with following:
// read all lines from file //
var file_contents = File.ReadAllLines("myfile.txt");
// set initial data list length to number of lines for better performance
var data = new List<double>(file_contents.Length);
// list for incorrect line numbers
var incorrectRows = new List<int>();
foreach (var x in file_contents.Select((s, i) => new {s, i}))
{
// x.s - line string
// x.i - line number
double value;
if (double.TryParse(x.s, out value))
data.Add(value); // add value, which was OK
else
incorrectRows.Add(x.i); // add index of incorrect value
}
That will prevent an exception at all and will give you line numbers for all incorrect values. It also iterate over file_contents just once and every value is being parsed only once.
I need for example the number of list-items, that are NOT "".
ATM, I solve it like this:
public int getRealCount()
{
List<string> all = new List<string>(originList);
int maxall = all.Count;
try
{
for (int i = 0; i < maxall; i++)
{
all.Remove("");
}
}
catch { }
return all.Count;
}
No question, performance is pretty bad. I'm lucky it's just a 10-items-list, but on a phone you should avoid such code.
So my question is, how can I improve this code?
One idea was: there could already be a method for that. The econd method would be: that all could be filled with only the items that are not "".
How should I solve this?
Thanks
Sounds like you want:
return originList.Count(x => x != "");
There's no need to create a copy of the collection at all. Note that you'll need using System.Linq; in your using directives at the start of your source code.
(Note that you should not have empty catch blocks like that - it's a terrible idea to suppress exceptions in that way. Only catch exceptions when you either want to really handle them or when you want to rethrow them wrapped as another type. If you must ignore an exception, you should at least log it somewhere.)
If performance is your concern, then you should keep a collection that is only for these items.
If performance is not a big deal, I would suggest you use a Linq query on your collection. The cool thing about Linq is that the search is delayed until you need it.
int nonEmptyItemCount = originList.Count(str => !string.IsNullOrEmpty(str));
You could also do
int nonEmptyItemCount = originList.Count(str => str != "");
You should use LINQ. Install ReSharper, it'll generate it for you.
Also, don't create an int maxall = all.Count and then use it in your for loop.
For mobile apps you shouldn't use unnecessary memory so just use all.Count in the for loop.
You're calling all.remove("") for every item in the list all. Why not just call it once? You're not using i at all in your code...
Why not:
public int getRealCount()
{
List<string> all = new List<string>(originList);
int erased =all.RemoveAll(delegate(string s)
{
return s == "";
});
return all.Count - erased;
}
Update:
Fixed the issue I had. This is without lambda's.
I am working on a project, and I have hit a brick wall. My code adds dates with a type to a database, but I need to create an error when there is already a similar entry within the code. However, I cannot get my loop to check for duplicates, and instead it adds duplicates! I am not very good at loops so I'm a bit stuck at this. Any help to check for duplicate entries and to stop it from creating too many would be a great help! Changed my code within this text area so it's not exactly the same variable names.
Here is my code: -
if (DT != null && DT.Length > 0 || DF != null && DF.Length > 0)
{
for (int t = 0; t < Type.Length; t++)
{
DateTime checkDate;
if (Type.IsSelectionValid(8, out typeError) && DateTime.TryParse(DF, out typeError) && DateTime.TryParse(DT, out checkDate))
{
TypeValid = true;
error.Error = false;
}
else
{
error.Errors = "Type-Invalid";
absenceTypeValid = false;
break;
}
}
else
{
error.Errors = "Type-Duplicate";
TypeValid = false;
break;
}
}
}
I'm 'fairly' sure you are going out of your way to make a problem more difficult than it is here, but I can't say for sure since I'm not entirely sure what this is doing.
But here are the conditions that need to be met to get to your Type-Duplicate Error line:
1) Either DT or DF have to not be empty to get past the first if statement
2) Either IsSelectionValid() has to return false or either DT or DF have to be an invalid DateTime.
None of those things constitute a duplicate.
Let me try to explain what I see here:
I first see variables called DT, DF. I can see these are dates, but that's all I know about them. I see 'Type' which I understand even less about than DT and DF. I see that you are doing a loop for Type.Length number of iterations... but what does this mean to me if I don't have a clue what Type is?
If you had comments explaining what things are I 'might' be able to help you, but there's just really not enough information to know what's happening here.
If you simply want to know how to avoid adding duplicates to a database, then I would suggest adding a constraint or index to the column in the database and then you can just catch the exceptions that are thrown when you try to insert a duplicate and deal with it that way. Alternatively, account for it in your insert statement.