We all know (hopefully) that C# is a garbage-collected language. In
general, what this means is that we as programmers don't need to free
our own memory - the garbage collector will free that memory for us once
it is no longer being referenced. Now, of course, garbage collection is
a lot more complicated than that, and writing a good garbage collector
is actually a relatively hard problem. And the fact that writing a
perfect garbage collector is probably impossible is the reason why
things like C#'s Weak Reference object exist.
Generally, when you talk about a reference to an object in .NET (and in
most other garbage collected languages), you are talking about a
"strong" reference. As long as that reference exists, the garbage
collector won't collect the object behind that reference. A weak
reference is a special type of reference where the garbage collector can
still collect the underlying object, even though you still technically
have a reference to it.
The key here is to remember that the garbage collector is not running
all the time. As far as we, the programmers of an application, know it
is completely random and could kick in at any time. This means that an
object only referenced through a weak reference could sit around for
long time, or for virtually no time at all (and really, it is even more
complicated than trying to figure out the next time the garbage
collector will run - because the garbage collector for C# is
generational).
And, as soon as you copy the reference out of the weak reference
variable into a regular reference, the underlying object will no longer
be collected (assuming that it hadn't already been collected), because
now you have a strong reference to it.
Ok, enough with this theoretical talk. Let's get down to some code, and
hopefully we can show how this weak reference object is actually useful.
public string _FilePath = "PathToMyImportantFile.dat";
public WeakReference _FileWeakRef = new WeakReference(null);
public List<double> ImportantBigFileContents
{
get
{
List<double> fileStrongRef = _FileWeakRef.Target as List<double>;
if (fileStrongRef == null)
{
using (StreamReader r = new StreamReader(_FilePath))
fileStrongRef = ParseImportantData(r);
_FileWeakRef.Target = fileStrongRef;
}
return fileStrongRef;
}
}
Say I had a large chunk of external data that would be handy to keep in
memory, but really isn't used very often (or maybe it is used a bunch in
bursts). This is exactly what the weak reference object is good for. In
the code above, I am storing the parsed version of some "Important Data"
in a
WeakReference
variable. What this means is that when someone
tried to access the ImportantBigFileContents
, the parsed data may or
may not still be in memory.
So first, we try and pull the reference out of the
_FileWeakRef
object. If that is null, we load the data from the file, parse it, store
it and hand it back. Otherwise we hand back what we got from the weak
reference variable. So this means that sometimes, the data will be in
memory, but other times the code will have to go out and reload it. This
doesn't make sense to do in all cases (or even in many cases), but if
the data is accessed in bursts, and you really didn't want to keep it in
memory all the time anyway, this gives you what you need with very
little extra work (the garbage collector does your management for you).
Now, there is a couple of common tear-your-hair-out mistakes that can be
made when using weak references. See if you can tell what is wrong with
the code below (and remember, the garbage collector might run at any
time):
public List<double> ImportantBigFileContents
{
get
{
if (_FileWeakRef.Target == null)
using (StreamReader r = new StreamReader(_FilePath))
_FileWeakRef.Target = ParseImportantData(r);
return _FileWeakRef.Target as List<double>;
}
}
Figure it out? Yup, the garbage collector could run during this property
access - cleaning up the memory for this weak ref object just as we were
about to hand it back:
public List<double> ImportantBigFileContents
{
get
{
if (_FileWeakRef.Target == null)
using (StreamReader r = new StreamReader(_FilePath))
_FileWeakRef.Target = ParseImportantData(r);
/* Garbage colllector could run right here. Whoops!*/
return _FileWeakRef.Target as List<double>;
}
}
So always remember to pull the reference out into a strong (or regular)
reference before you do any manipulation or checking - otherwise, stuff
could change out right from under you.
Well, that's all for an intro to the weak reference object. For you Java
developers out there, you actually have an equivalent (and it works
almost exactly the same) - the
WeakReference
class.
Comments
Post a Comment