Lesson 6: Dealing With null
Introduction
In both .NET and in Java, the keyword null
is used to indicate that a variable or argument does not
refer to a valid object.
The proxy types are designed in such a way that any place that accepts a Java null
value
accepts a .NET null
value.
This design allows you to write code like:
MyProxyType pt = new MyProxyType(); if( pt.field == null ) { Console.Error.WriteLine( "Field value is null!" ); } pt.field = null;
You can of course also use null
as a method argument if the underlying
Java method accepts null
as a value.
When a Proxy Becomes null
In contrast to the unmanaged C++ bindings, you should never have to worry about a .NET proxy instance
internally having a Java null
value. If a Java API returns a null
value, the corresponding
.NET resultd will be null
, not a proxy object that references null
.
That being said, it is possible to get into situations where a previously valid proxy instance
has become invalid. One such situation can arise if the using
statement is employed
incorrectly.
For background, all proxy objects implement the .NET IDisposable
interface and its
Dispose()
method. The Dispose()
method is usually called by the proxy
instance's object finalizer during garbage collection to release the Java reference being held by it.
You can explicitly call Dispose()
to release the held Java reference or you can use the
using
statement to have the .NET runtime call Dispose()
at a defined point.
The following example illustrates the proper use of using
.
using( MyProxyType pt = new MyProxyType() ) {
pt.DoSomething();
}
In this snippet, the .NET runtime will automatically invoke Dispose()
on pt
at the end of the using
block. Dispose()
will use JNI to free the held Java reference and
set the proxy instance's internal reference to null
to prevent a double free. This is totally
unproblematic because you have no reference to pt
anymore once the block is done.
But consider this slightly different, and totally improper, snippet:
MyProxyType pt = new MyProxyType();
...
using( pt ) {
pt.DoSomething();
}
...
if( pt != null )
pt.DoSomething();
The problem with this snippet is that it is essentially equivalent to:
MyProxyType pt = new MyProxyType(); ... try { pt.DoSomething(); } finally { pt.Dispose(); } ... if( pt != null ) pt.DoSomething();
As you can see, the Java reference maintained internally by pt
will be null
after the call to Dispose()
, but pt
itself will still hold a valid object
reference. So it will pass the null
test at the end and try to invoke the proxy method
DoSomething()
. Unfortunately, at that point, the Java reference on which it tries to invoke
the method will be null
and the result will be a Java NullPointerException
.
It should be noted that this problem is entirely due to improper usage of the using
statement, or better, on improper usage of an object after it has been disposed of by a using
statement.
Don't let this discussion drive you away from liberally employing using
statements in your programs.
Releasing Java references as early as possible can have a huge positive impact on your application's performance,
particularly in tight loops. Consider this snippet:
for( int i=0; i<10000000; i++ ) { using( MyProxyType pt = MyFactory.GetInstance( i ) ) { pt.DoSomething(); } }
We are essentially creating ten million Java instances, just to call one method on each one, and then we
can forget about it again. Without using
or a manual call to Dispose()
, these Java
references will sit around for a long time, possibly gradating into long-lived objects that are more expensive
to garbage collect. With using
, every instance immediately becomes eligible for collection right
after it has been used. Admittedly, this is a constructed example, but the performance impact really can be
dramatic in tight loops (database result sets, etc.).