Show line number in exception handling - c#

How would one display what line number caused the error and is this even possible with the way that .NET compiles its .exes?
If not is there an automated way for Exception.Message to display the sub that crapped out?
try
{
int x = textbox1.Text;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}

Use ex.ToString() to get the full stack trace.
You must compile with debugging symbols (.pdb files), even in release mode, to get the line numbers (this is an option in the project build properties).

To see the stacktrace for a given Exception, use e.StackTrace
If you need more detailed information, you can use the System.Diagnostics.StackTrace class (here is some code for you to try):
try
{
throw new Exception();
}
catch (Exception ex)
{
//Get a StackTrace object for the exception
StackTrace st = new StackTrace(ex, true);
//Get the first stack frame
StackFrame frame = st.GetFrame(0);
//Get the file name
string fileName = frame.GetFileName();
//Get the method name
string methodName = frame.GetMethod().Name;
//Get the line number from the stack frame
int line = frame.GetFileLineNumber();
//Get the column number
int col = frame.GetFileColumnNumber();
}
This will only work if there is a pdb file available for the assembly. See the project properties - build tab - Advanced - Debug Info selection to make sure there is a pdb file.

If you use 'StackTrace' and include the .pdb files in the working directory, the stack trace should contain line numbers.

string lineNumber=e.StackTrace.Substring(e.StackTrace.Length - 7, 7);

this way you can Get Line number from Exception
public int GetLineNumber(Exception ex)
{
const string lineSearch = ":line ";
var index = ex.StackTrace.LastIndexOf(lineSearch);
int ln=0;
if (index != -1)
{
var lineNumberText = ex.StackTrace.Substring(index + lineSearch.Length);
string lnum = System.Text.RegularExpressions.Regex.Match(lineNumberText, #"\d+").Value;
int.TryParse(lnum,out ln);
}
return ln;
}

Line numbers will be included in the stack trace if the library which generated the exception is compiled with debug symbols. This can be a separate file (*.pdb) or embedded in the library.
For .NET Core, .NET 5 and later, to have full exception line numbers in release builds, configure the project as follows:
<PropertyGroup>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<!-- Only enable the following if the line numbers mismatch -->
<!--<Optimize>false</Optimize>-->
<!--
Additional properties which may impact how printed line numbers match the source code line numbers are listed here:
https://learn.microsoft.com/en-us/dotnet/core/run-time-config/compilation
-->
</PropertyGroup>
The above configuration will include debug symbols directly with the built files, which can be published as nugets.
An alternative to the above is to restore debug packages together with the main nuget packages, which is currently not yet supported: https://github.com/NuGet/Home/issues/9667
Now get the exception line numbers:
try
{
throw new Exception();
}
catch (Exception ex)
{
// Get stack trace for the exception with source file information
var st = new StackTrace(ex, true);
// Get the top stack frame
var frame = st.GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
}

Is it possible to simply get the top frame from the StackTrace exposed by ex?
try
{
throw new Exception();
}
catch (Exception ex)
{
// Get the top stack frame
var frame = ex.StackTrace().GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
}

Related

Why is try catch sometimes ignored?

One example is this code:
try
{
string domain = o.SelectToken("response[" + i + "].domain").ToString();
...
}
catch(Exception)
{
continue;
}
Instead of just going on in the loop("continue"), vs halts and points at string domain = o.SelectToken("response[" + i + "].domain").ToString(); for an System.IndexOutOfRangeException.
Why is that?
You probably have 'break on all exceptions' selected in the Debug>Windows>Exception settings:
https://learn.microsoft.com/en-us/visualstudio/debugger/managing-exceptions-with-the-debugger?view=vs-2019
Unselecting this will let VS proceed.
You can do it by using two ways.
As suggested in MSDN, is to set it up in your Visual Studio (I believe it's 2019)
Debug > Windows > Exception Settings: Search for index and untick.
Please add exception to handle exception in your code..
catch(IndexOutOfRangeException e)
{
// handle it like logging it in file and continue
continue;
}
catch(Exception)
{
continue;
}

Pulling specific information from C# WinForms error messages

I have a logging system set up in my C# WinForms project that writes to a log txt file. A typical error message looks like this:
Error :
8:34:48 AM Tuesday, April 21, 2020
:
:System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.Collections.ArrayList.get_Item(Int32 index)
at System.Windows.Forms.DataGridViewColumnCollection.get_Item(Int32 index)
at CrossReferenceTool.frmXRefTool.DefineControlsLayout() in <PathToSourceCsFile>:line 306
Is there any way to grab parts of that error message? Specifically, I'd like to pull the offending method (DefineControlsLayout() in this case) and the line number.
Instantiate a new StackTrace and go log the details from that after any exception occurs.
Original link I used: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace.getframe?view=netframework-4.8
Example code
using System.Diagnostics;
try
{
throw new Exception("Fail");
}
catch (Exception e)
{
StackTrace st = new StackTrace(e);
// Display the most recent function call.
StackFrame sf = st.GetFrame(0);
Console.WriteLine();
Console.WriteLine(" Exception in method: ");
Console.WriteLine(" {0}", sf.GetMethod());
if (st.FrameCount > 1)
{
// Display the highest-level function call
// in the trace.
sf = st.GetFrame(st.FrameCount - 1);
Console.WriteLine(" Original function call at top of call stack):");
Console.WriteLine(" {0}", sf.GetMethod());
// will only work if .pdb is included
var lineNumber = sf.GetFileLineNumber();
var fileName = sf.GetFileName();
}
}
If you are already catching an Exception type then you have sub properties such as
Exception.StackTrace
Exception.TargetSite
I don't think that you can use them to pull the line of code though.

How do I show the line number and frame in a catch exception?

I'm trying to write the line number and frame to a text file but I cannot get it to work. From what I've read online the way I've written this should work but it's not actually outputting any line numbers making my debugging quite hard. Can anyone assist in perhaps pointing out where my code could be wrong?
catch (Exception e)
{
var st = new StackTrace(e, true);
var frame = st.GetFrame(0);
var line = frame.GetFileLineNumber();
var sw = new System.IO.StreamWriter(filename, true);
sw.WriteLine(
DateTime.Now.ToString() + "\r\n"
+ e.Message + "\r\n"
+ e.InnerException + "\r\n"
+ e.Source + "\r\n"
+ frame + "\r\n"
+ line);
sw.Close();
}
It does output some information just not the line / frame numbers.
Here is an example of what's getting output.
22/08/2016 08:34:24
Input string was not in a correct format.
StringToNumber at offset 12099653 in file:line:column <filename unknown>:0:0 0
Also please note the application is running in Debug not Release.
Make sure your application is built in DEBUG mode. If it is in Release, than some information like line numbers are not included in exception texts.
Another possible reason is that you edited Debug configuration for your project and disabled debug information (Project settings => Build => Advanced => Output debug info )
And finally you need pdb files for line numbers and they should be in same folders as your .exe / .dll. By default, they are there, until you manually remove them or copy parts of your application to another location and run there.

Best way StreamReader skipping Null or WhiteSpace line

After searching and trying the different ways I found I either wasn't happy with the way I was doing the code or it didn't work right for me. I'm new at programming so my understanding is limited. Please keep in mind with the answer.
I want to read a .csv file line by line and skipping lines that are blank. With the contents of the lines I want to put into a list of object. I have everything working except for the skipping line part. Also any feedback about improving any parts of my code are all welcome. I like constructive criticism.
public void CardaxCsvFileReader()
{
string cardaxCsvPath = (#"C:\Cardax2WkbTest\Cardax\CardaxTable.csv");
try
{
using (System.IO.StreamReader cardaxSR =
new System.IO.StreamReader(System.IO.File.OpenRead(cardaxCsvPath)))
{
string line = "";
string[] value = line.Split(',');
while (!cardaxSR.EndOfStream)
{ // this commented out part is what I would like to work but doesn't seem to work.
line = cardaxSR.ReadLine();//.Skip(1).Where(item => !String.IsNullOrWhiteSpace(item));
value = line.Split(',');
if (line != ",,,,,") // using this as temp to skip the line because the above commented out part doesn't work.
{
CardaxDataObject cardaxCsvTest2 = new CardaxDataObject();
cardaxCsvTest2.EventID = Convert.ToInt32(value[0]);
cardaxCsvTest2.FTItemID = Convert.ToInt32(value[1]);
cardaxCsvTest2.PayrollNumber = Convert.ToInt32(value[2]);
cardaxCsvTest2.EventDateTime = Convert.ToDateTime(value[3]);
cardaxCsvTest2.CardholderFirstName = value[4];
cardaxCsvTest2.CardholderLastName = value[5];
Globals.CardaxQueryResult.Add(cardaxCsvTest2);
}
}
}
}
catch (Exception)
{
myLog.Error("Unable to open/read Cardax simulated punch csv file! " +
"File already open or does not exist: \"{0}\"", cardaxCsvPath);
}
EDITED
If you are lines are not truly blank and contain commas, you can split with RemoveEmptyEntries option and then check the column count.
while (!cardaxSR.EndOfStream)
{ // this commented out part is what I would like to work but doesn't seem to work.
line = cardaxSR.ReadLine();//.Skip(1).Where(item => !String.IsNullOrWhiteSpace(item));
value = line.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries); // <-- Remove empty columns while splitting. It has a side-effect: Any record with just a single blank column will also get discarded by the if that follows.
if (value.length < 6)
continue;
CardaxDataObject cardaxCsvTest2 = new CardaxDataObject();
cardaxCsvTest2.EventID = Convert.ToInt32(value[0]);
cardaxCsvTest2.FTItemID = Convert.ToInt32(value[1]);
cardaxCsvTest2.PayrollNumber = Convert.ToInt32(value[2]);
cardaxCsvTest2.EventDateTime = Convert.ToDateTime(value[3]);
cardaxCsvTest2.CardholderFirstName = value[4];
cardaxCsvTest2.CardholderLastName = value[5];
Globals.CardaxQueryResult.Add(cardaxCsvTest2);
}
Another improvement feedback I have: When you catch an exception, it's a good practice to log the exception in addition to your custom error line. A custom error line might be good for say website users, but as a developer running some service you will appreciate the actual exception stack trace. It will help you debug a bug easier.
catch (Exception ex)
{
myLog.Error("Unable to open/read Cardax simulated punch csv file! " +
"File already open or does not exist: \"{0}\".\r\n Exception: {1}", cardaxCsvPath, ex.ToString());
}
Just check if value.Length == 6, this way it'll skip lines which don't contain enough data for your columns
Use a dedicated CSV parser, such as the EasyCSV class available here*:
https://github.com/jcoehoorn/EasyCSV
public void CardaxCsvFileReader()
{
try
{
string cardaxCsvPath = (#"C:\Cardax2WkbTest\Cardax\CardaxTable.csv");
Globals.CardaxQueryResult =
EasyCSV.FromFile(cardaxCsvPath)
.Where(r => r.Any(c => !string.IsNullOrEmpty(c)))
.Select(r => CardaxDataObject() {
cardaxCsvTest2.EventID = int.Parse(r[0]),
cardaxCsvTest2.FTItemID = int.Parse(r[1]),
cardaxCsvTest2.PayrollNumber = int.Parse(r[2]),
cardaxCsvTest2.EventDateTime = DateTinme.Parse(r[3]),
cardaxCsvTest2.CardholderFirstName = r[4],
cardaxCsvTest2.CardholderLastName = r[5]
}).ToList();
}
catch (Exception)
{
myLog.Error("Unable to open/read Cardax simulated punch csv file! " +
"File already open or does not exist: \"{0}\"", cardaxCsvPath);
}
}
I also recommend re-thinking how you structure this. The code below is better practice:
public IEnumerable<CardaxDataObject> ReadCardaxCsvFile(string filename)
{
//no try block at this level. Catch that in the method that calls this method
return EasyCSV.FromFile(cardaxCsvPath)
.Where(r => r.Any(c => !string.IsNullOrEmpty(c)))
// You may want to put a try/catch inside the `Select()` projection, though.
// It would allow you continue if you fail to parse an individual record
.Select(r => CardaxDataObject() {
cardaxCsvTest2.EventID = int.Parse(r[0]),
cardaxCsvTest2.FTItemID = int.Parse(r[1]),
cardaxCsvTest2.PayrollNumber = int.Parse(r[2]),
cardaxCsvTest2.EventDateTime = DateTinme.Parse(r[3]),
cardaxCsvTest2.CardholderFirstName = r[4],
cardaxCsvTest2.CardholderLastName = r[5]
});
}
Suddenly the method boils down to one statement (albeit a very long statement). Code like this is better, because it's more powerful, for three reasons: it's not limited to using just the one input file, it's not limited to only sending it's output to the one location, and it's not limited to only one way to handle errors. You'd call it like this:
try
{
string cardaxCsvPath = (#"C:\Cardax2WkbTest\Cardax\CardaxTable.csv");
Globals.CardaxQueryResult = ReadCardaxCsvFile(cardaxCsvPath).ToList();
}
catch (Exception)
{
myLog.Error("Unable to open/read Cardax simulated punch csv file! " +
"File already open or does not exist: \"{0}\"", cardaxCsvPath);
}
or like this:
try
{
string cardaxCsvPath = (#"C:\Cardax2WkbTest\Cardax\CardaxTable.csv");
foreach (var result in ReadCardaxCsvFile(cardaxCsvPath))
{
Globals.CardaxQueryResult.Add(result);
}
}
catch (Exception)
{
myLog.Error("Unable to open/read Cardax simulated punch csv file! " +
"File already open or does not exist: \"{0}\"", cardaxCsvPath);
}
I also recommend against using a Globalsclass like this. Find a more meaningful object with which you can associate this data.
* Disclaimer: I am the author of that parser

Trouble Moving files in C#?

I am making a software that will move files from the downloads folder to a specific sub folder in a directory. The sub folder is selected by the user by a combobox. I keep getting this error: System.IO.IOException: Cannot create a file when that file already exists. Also, these error come up on people's computer who install my program...exceptions and things. How do i turn it off. Also, why do i get this error? Here is my code:
string pathUser4 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string pathDownload4 = (pathUser4 + #"\Downloads\");
string sourceFile = pathDownload4 + listBox1.Text;
string pathdoc5 = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string pathDownload5 = (pathdoc5 + #"\iracing\setups\");
string destinationFile = pathDownload5 + comboBox1.Text;
File.Move(sourceFile, destinationFile);
if (comboBox1.Text == "Select File Destination")
{
MessageBox.Show("Please Select A Destination Folder", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Each File.Move should be wrapped in a try/catch block as you can never expect an IO operation to execute without error. It could be something as simple as the user having a file handle open, or the file existing in the destination folder, either way, you don't want a single file to throw an exception that stops the entire operation. You will want to catch the exceptions and log them either to an error log file or to the event log, this way you can see the errors that occurred but it will not interrupt anything.
Secondly, for any desktop application I would add global error handling to log any uncaught errors. You can do this by putting this code at the beginning of your program,
AppDomain.CurrentDomain.UnhandledException += (a, exception) => File.AppendAllText("errorlog.txt", exception.ToString() + "\n"
This will keep the user from ever seeing ugly exceptions being thrown. Also be sure you are not giving the users the .pdb files as this will cause exceptions to contain paths of the computer it was compiled on which can contain your username and other sensitive information you wouldn't want a client to see.
You can register the global exception handling when the main window is initialized, you want it to be the first thing you do before any thing else because again you never know when an exception will be thrown so you have to think defensively.
public partial class MainWindow : Window
{
public MainWindow()
{
AppDomain.CurrentDomain.UnhandledException += (a, exception) => File.AppendAllText("errorlog.txt", exception.ToString() + "\n");
InitializeComponent();
}
}
C# uses exceptions extensively so it will be good concept for you to study up on if you are not familiar with this type of error handling. All exceptions derive from the Exception class so when you write catch (Exception e) this will catch all exceptions (because a base reference can hold an object of a derived type), however if you know the specific exception a method will throw you can catch a more specific exception (always before the more general catch) and handle it in a specific way. In this example you may have an IOException from the File.Move() that you want to catch and handle differently.
try
{
string pathUser4 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string pathDownload4 = (pathUser4 + #"\Downloads\");
string sourceFile = pathDownload4 + listBox1.Text;
string pathdoc5 = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string pathDownload5 = (pathdoc5 + #"\iracing\setups\");
string destinationFile = pathDownload5 + comboBox1.Text;
File.Move(sourceFile, destinationFile);
if (comboBox1.Text == "Select File Destination")
{
MessageBox.Show("Please Select A Destination Folder", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception e)
{
File.AppendAllText("ErrorLog.txt", e.ToString() + "\n");
}
The example code from MSDN for File.Move should get you pointed at the various things you need to deal with, such as an already existing file and basic error handling.
using System;
using System.IO;
class Test
{
public static void Main()
{
string path = #"c:\temp\MyTest.txt";
string path2 = #"c:\temp2\MyTest.txt";
try
{
if (!File.Exists(path))
{
// This statement ensures that the file is created,
// but the handle is not kept.
using (FileStream fs = File.Create(path)) {}
}
// Ensure that the target does not exist.
if (File.Exists(path2))
File.Delete(path2);
// Move the file.
File.Move(path, path2);
Console.WriteLine("{0} was moved to {1}.", path, path2);
// See if the original exists now.
if (File.Exists(path))
{
Console.WriteLine("The original file still exists, which is unexpected.");
}
else
{
Console.WriteLine("The original file no longer exists, which is expected.");
}
}
catch (Exception e)
{
Console.WriteLine("The process failed: {0}", e.ToString());
}
}
}
The error may caused by your code, or by some invalid input.
As #Despertar mentioned, I suggest all the program include error handling and log features in your code. It will be very helpful for your debug.
But I suggest use open source log library, not do it by yourself. For example, log4net, NLog, etc.

Categories

Resources