Introduction
This is my first CodeProject article, so, do not expect too much of it! Also, English is not my first language, so, excuse me if I have made any mistakes.
Why do we need to log everything? If you need to trace some class activity, you'll need to log its steps. It is very simple to do it from the GUI, but what if I want to re-use my class in another project? Well, I will have to rewrite all logs. "No way, man!" you'll say... "Use a logger class!!"... Yes, but then your class is dependant too... I used the Snippet Compiler for my tests. This is a .NET must-have tool!
The Idea
I needed to simply log a string message from the inside of my class, without having to depend on an external class. I wanted to do something like this:
class SomeClass
{
...
void DoSomething()
{
...
LogMessage("Something to log");
}
}
Where LogMessage(String) simply puts that string out of my class, and makes some other function handle it... hey, that sounds familiar to me... Callbacks! But wait, I'm talking in .NET, I have to say 'Delegates'.
The Solution
If you haven't used Delegates so far, you don't really know what a delegate is. Because all well-known 'events', are in fact delegates. Delegates are very useful when you have to pass a function as a parameter, instead an object. C++ calls this a 'Callback'. Other languages let you pass the function as it is (but we are talking about .NET!). You may find this code in the zip file as LoggerImplementation.cs. I will declare a Delegate for my logger:
public delegate void LogDelegate(String msg);
And now, I will add some code to my class to handle it:
private LogDelegate _log;
public LogDelegate Logger
{
get { return _log; }
set { _log = value; }
}
private void LogMessage(String msg)
{
if( _log != null )
{
_log(msg);
}
}
Cool! Now I can call LogMessage("Something!") from any method inside my class without using external objects. We first declared a private LogDelegate, and then the property to access it. Note that here it is not necessary to have the member and the property, because there is no logic in it. But it is always a good practice to do it. Then, there is a LogMessage(String) method. This is a pre-call to the Delegate, because we have to check if someone is consuming the delegate before launching it. Oh yes, who's showing the messages? I see no print sentences... Well, the one who instantiates is the one who has to tell the object what to do when a LogMessage is called. This is done like this:
stuffMaker.Log += delegate ( String msg )
{
Console.WriteLine(msg);
};
This will work only in C# 2.0. If you are using previous versions, you cannot declare anonymous methods. So, you'll have to add a new LogDelegate to Log, addressing another named method which will handle the delegate (upgrade yourself, use 2.0). So, you also want to write the message to a file? Or the Windows EventLog? No problem, delegates let you add many different handlers of a single method.
stuffMaker.Log += delegate ( String msg )
{
Console.WriteLine(msg);
};
stuffMaker.Log += delegate ( String msg )
{
Console.WriteLine("Other log: " + msg);
};
stuffMaker.Log += delegate ( String msg )
{
Console.WriteLine("Yes, another log: " + msg);
};
stuffMaker.Log += delegate ( String msg )
{
};
A Better 'Object Oriented' Solution
Well, you may say "But I have to paste this code in every class I want to log ...". Well, yes. You're right... so, why don't we do it the OO way?? Let's define an abstract class (you may find this code in the zip file as LoggerImplementation Extender.cs).
public delegate void LogDelegate(String msg);
public abstract class Logger
{
private LogDelegate _log;
public LogDelegate Log
{
get { return _log; }
set { _log = value; }
}
protected void LogMessage(String msg)
{
if( _log != null )
{
_log(msg);
}
}
}
And now, the only thing you have to do to add logging capabilities to a class is this:
public class StuffMaker : Logger
Conclusion
This is my first article and I hope you liked it. I've found this very useful in my daily work, and also for testing purposes. You may use this same approach for monitoring class' internal processes too. You may also note that this Pattern doesn't even exist... but it was a nice solution to a common problem, and I gave it a name.
History
- 12th June, 2006: Initial post