The Best practices for exceptions document on MSDN says that you can have an exception builder method inside your class if the same exception is to be used in many parts of the class. But also, it says that in some cases, it's better to use the exception's constructor.
Let's say I have the following code in an UserData class:
private MailAddress _addr;
public UserData(string emailAddress)
{
// Tries to validate the e-mail address
try
{
_addr = new MailAddress(emailAddress);
}
catch
{
throw new ArgumentException(nameof(emailAddress), "Invalid email address.");
}
if (_addr.Address != emailAddress)
{
throw new ArgumentException(nameof(emailAddress), "Invalid email address.");
}
}
You can see that in both throw statements, I'm throwing the exact same exception.
The question is: Is it correct to add an exception builder method to get my exception and throw that? Will I get the correct stacktrace and such if I do so? And if not, how do I determine between exception builders and constructors?
Is it correct to add an exception builder method to get my exception and throw that
That depends. As suggested in the article you linked: If it's the same exception (with the same information), it makes sense to create such a helper method to keep your code clean.
Will I get the correct stacktrace and such if I do so
Yes, you will.
Take a look at this example. (DotNetFiddle).
public static void Main()
{
try{
throw CreateEx("Hi");
} catch(Exception ex) {
Console.WriteLine(ex.ToString());
}
try {
CreateEx2("Hi");
} catch(Exception ex) {
Console.WriteLine(ex.ToString());
}
}
public static Exception CreateEx(string text){
text += " Additional text";
return new ArgumentOutOfRangeException(text);
}
public static void CreateEx2(string text){
text += " Additional text";
throw new ArgumentOutOfRangeException(text);
}
The stacktrace depends on where the exception is thrown, not where it is built.
System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: Hi Additional text
at Program.Main() in d:\Windows\Temp\b4ln3dbq.0.cs:line 13
System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: Hi Additional text
at Program.CreateEx2(String text) in d:\Windows\Temp\b4ln3dbq.0.cs:line 34
at Program.Main() in d:\Windows\Temp\b4ln3dbq.0.cs:line 19
Related
I'm trying to create a validation strategy for user input. But I keep getting a CS0155 error.
I've tried throwing an exception but it doesn't get rid of the error.
catch (OverflowAction)
{
Debug.WriteLine(
"{0}.Validate: Int32 overflow (\"{1}\").",
GetType(), str);
string errmsg = Properties.Resources.OverflowError;
return new ValidationResult(false, errmsg);
//throw new NotImplementedException();
}
I expect the validator to catch the exception and return an error message.
This error indicates that your OverflowAction class does not inherit from Exception (or derived one).
See CS0155 error documentation.
Only data types that derive from System.Exception can be passed into a catch block.
OverflowAction should looke like
class OverflowAction : Exception
{
// ...
}
You might be confusing OverflowAction with OverflowException ...
I would like to pass multiple error messages to the GUI. How do I have to do this? Please have a short look at my abstract example above:
try
{
LogIn(usr, pwd); //entry point
}
catch(Exception ex)
{
throw new Exception("Login failed.");
}
public void LogIn(string usr, string pwd) {
if(usr == "") {
throw new Exception("Username was empty.");
}
if(pwd== "") {
throw new Exception("Password was empty.");
}
try
{
//do some other stuff without a more specific error message
}
catch
{
throw;
}
}
Later I would like to get a result error message like
Login failed. Password was empty.
if the user didn't type in a password. Right now I just get the last error message ("login failed.") on top and so just half of my information that I would like to give to the user.
You can nest exceptions:
try
{
LogIn(usr, pwd); //entry point
}
catch(Exception ex)
{
throw new Exception("Login failed.", ex);
}
Note the second argument, and the InnerException property of Exception.
But before doing do, consider whether the above block is adding any value. If you just let the Password was empty exception escape instead, the caller would still know, generically, that the login has failed, and that exception alone seems to contain all the required information.
Only catch an exception if you have something useful to do - if you can recover an error condition, add information or do not want to expose implementation details to your callers. Otherwise, let the exceptions rise to a level where something useful can be done.
I would rethink your structure.
As in the comments pointed out there are some things to consider:
Will the method called elsewhere and could so lead to wrong usage?
If I use exceptions in my flow control, could it lead to unreadable code? (Using exceptions for flow control)
Approach with List<string> for collecting issues:
public void LogIn(string usr, string pwd)
{
List<string> errors = new List<string>();
if(string.IsNullOrEmpty(usr))
{
errors.Add("Username is empty.");
}
if(string.IsNullOrEmpty(pwd))
{
errors.Add("Password is empty.");
}
if(errors.Count > 0) // If errors occur, throw exception.
{
throw new Exception(string.Join("\r\n",errors));
}
}
You could just use ex.Message which would either be Password was empty. or Username was empty.
I have the following code:
finally
{
if (!isDnsSet)
{
var exception = new Exception(<DNS-INFORMATION>);
localLog.TraceException(exception);
throw exception;
}
}
As it stands, this exception throws too much information to the user that is not particularly needed for them to see. I want to be able to log exception using my localLog class but also throw another exception with a more concise message.
I was thinking to just create another exception with the shortened message and still log the original, more verbose one using my class.
Is there a more elegant way of doing this or would I just do something like:
var shortException = new Exception(<short and sweet message>);
var longException = new Exception(<not so short and sweet but still useful for other devs>);
localLog.TraceException(longException);
throw shortException;
I think a cleaner method would be to make the longer exception an inner exception:
finally
{
if (!isDnsSet)
{
var innerException = new Exception(<not so short and sweet but still useful for other devs>);
var exception = new Exception(<short and sweet message>, innerException);
localLog.TraceException(exception);
throw exception;
}
}
That way you have consistency between the exception that's thrown and the exception that's logged, making diagnosis easier.
One approach is to create a custom exception that carries both a long and a short message. Users who get the exception outside your library would access the short message through Exception's Message property, while your TraceException method would access the long version through an additional property provided by your custom exception:
public class DetailedException : Exception {
public string DetailedMessage { get; }
public DetailedException(string longMessage, string shortMessage) : base(shortMessage) {
DetailedMessage = longMessage;
}
}
Inside TraceException method:
var message = (exception as DetailedException)?.DetailedMessage ?? exception.Message;
Couldn't the exception handler receive and process the longException and, as part of its function, throw the shortException?
I am trying out NUnit, Unit Testing and Integration testing for the first time. I have been reading up and doing lots of online courses. As I'm sure you know very well it's something knowing theory and doing it in practice.
I am stuck on a particular test. The application that I have is in C# .Net 3.5.
I am trying to assert that a method with a certain bad input will throw a particular exception.
When I run the method with the same input given to the test the expected exception is thrown.
The method being tested code is:
private static bool FilePathHasInvalidChars(string userInputPath)
{
try
{
Path.GetFullPath(userInputPath);//this is where the warning appears
}
catch (Exception e)
{
Log.Error(String.Format(
"The Program failed to run due to invalid characters or empty string value for the Input Directory. Full Path : <{0}>. Error Message : {1}.",
userInputPath, e.Message), e);
return true;
}
return false;
}
I want to check that the above code can catch an exception if the provided input directory is not meeting the criteria.
The Unit test that I have at the moment is:
[Test]
public void can_throws_exception_for_empty_string()
{
var testDirectory = "";
var sut = new DirectoryInfoValidator();
Assert.Throws<ArgumentNullException>(() => sut.FilePathHasInvalidChars(testDirectory));
}
The problem I have is that the test allways fails and if I check the return it states that It expected an ArgumentNull exception but was null. I have taken a screenshot of the output from the test:
Any idea what I might be doing wrong?
EDIT: By the way I have also attempted to use
[ExpectedException(typeof(ArgumentNullException), ExceptionMessage= "Log Message", MatchType=MessageMatch.Contains)]
Have had same result with that.
On an ending note I am not sure if this is considered an Integration test or a Unit test given that my method uses Path.GetFullPath(string directory). Anyway my main issue right now is understanding what I am doing wrong. :)
Many thanks,
Jetnor.
UPDATE: After taking all the points into consideration and looking at my system's needs I have decided not to throw an exception. Instead I have decided to create tests which cover the different possible exceptions that can occur in my situation. The test method looks like this:
[Test]
public void returns_true_for_empty_string()
{
var testDirectory = "";
var sut = new DirectoryInfoValidator();
var isInvalidPath = sut.FilePathHasInvalidChars(testDirectory);
Assert.That(isInvalidPath, Is.True);
}
This is a starting point. I inted to use the [TestCase] option by providing all the inputs to one test and checking all of them at the same time. Thanks for your help guys.
Your method FilePathHasInvalidChars does not throw an exception. An exception is thrown inside of your method, but your method catches and handles the exception. Your method will always return a valid value.
If you want your method to throw an ArgumentNullException rather than logging and swallowing it, try this:
private static bool FilePathHasInvalidChars(string userInputPath)
{
try
{
Path.GetFullPath(userInputPath);//this is where the warning appears
}
catch (ArgumentNullException) {
Log.Error("The Program failed to run due to a null string value for the Input Directory.");
throw;
}
catch (Exception e)
{
Log.Error(String.Format(
"The Program failed to run due to invalid characters or empty string value for the Input Directory. Full Path : <{0}>. Error Message : {1}.",
userInputPath, e.Message), e);
return true;
}
return false;
}
With this modification, if userInputPath is null your method will log and re-throw the ArgumentNullException, and your unit test will see the exception and pass.
Your code does not throw an ArgumentNullException. Based on your code, it should never* throw any exception- it should simply return true or false.
Change your test to the NUnit equivalent of:
Assert.IsTrue(() => sut.FilePathHasInvalidChars(testDirectory));
Or, if an empty string SHOULD throw an ArgumentNullException, modify the code to something like the following**:
private static bool FilePathHasInvalidChars(string userInputPath)
{
if(string.IsNullOrWhiteSpace(userInputPath) throw new
ArgumentNullException("userInputPath");
try
{
Path.GetFullPath(userInputPath);//this is where the warning appears
}
catch (ArgumentException e)
{
Log.Error(String.Format(
"The Program failed to run due to invalid characters or empty string value for the Input Directory. Full Path : <{0}>. Error Message : {1}.",
userInputPath, e.Message), e);
throw;
}
catch (Exception e)
{
Log.Error(String.Format(
"The Program failed to run due to invalid characters or empty string value for the Input Directory. Full Path : <{0}>. Error Message : {1}.",
userInputPath, e.Message), e);
return true;
}
return false;
}
*- For a value of "never" that means "so rarely you're unlikely to have to consider it"
**- I haven't tried to compile that, so there may be errors; it's just a starting point.
I have caught an exception and after catching it I have to append the method name so that I should know which method the error came from, and then throw it to another function and save it in database.
try
{
}
catch (Exception ex)
{
string strError = ex.Message.ToString() + "methodname:getNoOfRecordsForBatchProcess";
throw strError.ToString();
}
but it gives me error that you can't use string variable to throw exception.the throw exception only use with system exception. is there any way to handle this error.
The method name is visible in Exception.StackTrace property too.
By the way you may rely on some other way to recover its name using StackFrame Class, like for example:
private static string GetCallingMethodName()
{
const int iCallDeepness = 2; //DEEPNESS VALUE, MAY CHANGE IT BASED ON YOUR NEEDS
System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(false);
System.Diagnostics.StackFrame sframe = stack.GetFrame(iCallDeepness);
return sframe.GetMethod().Name;
}
An answer to your question:
throw new Exception(strError);
(However, as others have said, this might not be the best way to handle this.)
Possible duplicate of How to get the name of the method that caused the exception.
catch (Exception ex)
{
MethodBase site = ex.TargetSite;
string methodName = site == null ? null : site.Name;
...
}
When catching and rethrowing an exception it's 'recommended' to create a new Exception, with a meaningful error message, and pass the original exception in as the inner exception.
For example:
catch(Exception ex)
{
const string message = "Some error message.";
throw new MeaningfulException(message, ex);
}
I would also go for the StackFrame. I'm posting an extension to #Tigran's answer (because you asked for a bit more clarified usage inside the try{...}catch{...} block), so if this is helping you to understand the usage, please accept his answer, not mine:
try
{
int a = 0;
var r = 1 / a;
}
catch (Exception ex)
{
throw new Exception(
String.Format("{0} Method name: {1}",
ex.Message,
GetCallingMethodName()),
ex);
}
The GetCallingMethodName:
private static string GetCallingMethodName()
{
const int iCallDeepness = 1; //DEEPNESS VALUE, MAY CHANGE IT BASED ON YOUR NEEDS
System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(false);
System.Diagnostics.StackFrame sframe = stack.GetFrame(iCallDeepness);
return sframe.GetMethod().Name;
}
P.S. #Tigran, I will remove this answer if you consider that it's not needed.