First off, I think I know what's going on, but I thought I'd bring this issue up here for some discussion and see if anyone has an "answer" to this other than what I'm thinking. Because, it doesn't completely make sense to me.
What I found is that when creating a error log for exceptions, I was doing this and it wasn't working:
catch( Exception ex )
{
LogException( ex.Message );
if ( !string.IsNullOrEmpty( ex.InnerException.Message ) )
{
LogInnerException( ex.InnerException.Message );
}
}
and lo and behold, when I ran this I'd often get a NullRe开发者_StackOverflow中文版ferenceException. Huh?
I'm checking for null, right?
now, I have to use this:
if ( ex.InnerException != null && !string.IsNullOrEmpty( ex.InnerException.Message )
but that seems counter-intuitive and also counter productive. Because, heck, if I do this:
if ( !string.IsNullOrEmpty( null ) )
That doesn't give me any problems at all. And if ex.InnerException is null, then certainly ex.InnerException.Message is null, right?
Apparently not.
I wrote a complete console app that reproduces this. If you
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace stringisnullorempty
{
class Program
{
static void Main( string[] args )
{
if ( !string.IsNullOrEmpty( null ) )
{
Console.WriteLine( "Ha ha ha, right...." );
}
MyBClass bClass = new MyBClass();
bClass.BClassName = "Some B Class Name";
if ( !string.IsNullOrEmpty( bClass.AClass.AString ) ) //<== Exception occurs here.
{
Console.WriteLine( bClass.AClass.AString );
}
}
}
public class MyAClass
{
private string aString;
public string AString
{
get
{
return aString;
}
set
{
aString = value;
}
}
private int aValue;
public int AValue
{
get
{
return aValue;
}
set
{
aValue = value;
}
}
public MyAClass() { }
}
public class MyBClass
{
private MyAClass aClass;
public MyAClass AClass
{
get
{
return aClass;
}
set
{
aClass = value;
}
}
private string bClassName;
public string BClassName
{
get
{
return bClassName;
}
set
{
bClassName = value;
}
}
public MyBClass() { }
}
}
What I think is happening is that the code processes ex.InnerException.Message before trying to process the IsNullOrEmpty. Since ex.InnerException is null, we get an exception trying to access ex.InnerException.Message.
I'm wondering though, do I need the full check? Will the ex.InnerException != null be enough. If we have an inner exception, will we always have a message associated with it?
Thanks.
When you call ex.InnerException.Message
, it's not the message that is null, but rather then InnerException
object.
Think of it this way:
string temp = ex.InnerException.Message
// ^ the error is on this dot.
if (string.IsNullOrEmpty(temp))
{
...
}
To match exactly what you want to do, just use this:
catch (Exception ex) // PLEASE catch something more specific.
{
LogException(ex.Message);
if (ex.InnerException != null)
{
LogInnerException(ex.InnerException.Message);
}
}
In order to solve this problem, I have used this method in the past:
public Exception GetInnermost(Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
return ex;
}
ex.GetBaseException()
If InnerException
is null you cannot access one of its properties (Message
in your case). This is why you get the exception, and it could not be otherwise.
Try to think like this: how can string.IsNullOrEmpty
know the expression you are using to pass it a parameter? For the function, it is just a parameter.
These two forms are equivalent, but maybe the second will be clearer for you:
string.IsNullOrEmpty( ex.InnerException.Message ); // exception here
string test = ex.InnerException.Message; // exception HERE
string.IsNullOrEmpty(test);
Hope this is clear :)
Remember that the order of execution in C# is that parameters are evaluated before they're send through to the method method body (i.e., pushed on the stack). The instruction string.IsNullOrEmpty(x)
will first evaluate x
.
In your case, x
in the example is ex.InnerException.Message
. This is evaluated from left to right. If either ex
, or ex.InnerException
are null, a NullReferenceException is thrown.
Here's a way to work around this, because you know that ex
is never null, which will check the Message property of InnerException if there is any, or of Message if there is no InnerException:
if(string.IsNullOrEmpty((ex.InnerException ?? ex).Message))
{
// .. do something
}
But you probably just want to check whether there is an InnerException
in the first place and that can be done like so:
if(ex.InnerException != null) { ... }
Or you want to use either the exception or the inner exception message, with preference for InnerException if both are there, use this:
string exceptionMessage = (ex.InnerException ?? ex).Message;
Basically, any time you use a .
you've got a chance for a null reference exception. It's a pain, but yeah, you need to check InnerException
for null.
精彩评论