February, 2008

  • Kirill Osenkov

    Using delegates - a simple sample

    • 1 Comments

    A while ago Charlie Calvert did a great job explaining delegates here. Readers were asking for more samples that would demonstrate the benefits of delegates. After a lot of hard thinking (somehow it's hard to find an easy sample!) I've come up with a simple sample which will hopefully demonstrate just that. Sorry if you don't like it.

    Suppose you are designing some classes that have a DisplayHelp() method:

    public void DisplayHelp()
    {
        Console.WriteLine("tf get itemspec");
        Console.WriteLine(" ");
        Console.WriteLine(" Retrieves a read-only copy of a file ");
        Console.WriteLine(" from Team Foundation Server to the local workspace ");
        Console.WriteLine(" and creates folders on disk to contain it.");
    }

    See how Console.WriteLine is repeated every time? What if you have to output help to a file stream instead? You'll have to change all the lines of code.

    Solution: you could abstract away the logic of outputting from the actual help contents and pass this logic as a parameter:

    public void DisplayHelp(Action<string> print)
    {
        print("tf get itemspec");
        print(" ");
        print(" Retrieves a read-only copy of a file ");
        print(" from Team Foundation Server to the local workspace ");
        print(" and creates folders on disk to contain it.");
    }

    Now you can pass the output algorithm as an argument to the DisplayHelp method:

    command.DisplayHelp(Console.WriteLine);

    or, for printing to a file:

    using (var writer = new StreamWriter(@"c:\help.txt"))
    {
        command.DisplayHelp(writer.WriteLine);
    }

    From my experience, if you have a complex hierarchy with many classes, and each of them has an output method similar to DisplayHelp, this approach actually turns out quite useful. The more I learn about programming, the more I use delegates in more and more contexts.

    Note: An equally flexible approach would be using yield return to return the strings, but the yield return approach doesn't compose very well.

    Another note: we could actually redirect the standard output using the Console.SetOut() method, but that would defeat the whole purpose of demonstrating delegates :)

    So how do we know where to use delegates? Potentially you can use delegates wherever you use an interface with a single method (such as e.g. IDisposable). Whether you actually want to do so, remains a matter of personal preference.

  • Kirill Osenkov

    How to: override static methods

    • 11 Comments

    I know, I know. You can't override static methods. The title was just a trick to provoke your interest :-) In this post, I'll first try to explain why it is impossible to override static methods and then provide two common ways to do it.

    Or rather - two ways to achieve the same effect.

    So, what's the problem?

    There are situations where you wish you could substitute or extend functionality of existing static members - for example, provide different implementations for it and be able to switch implementations at runtime.

    For example, let's consider a static class Log with two static methods:

    public static class Log
    {
        public static void Message(string message){ ... }
        public static void Error(Exception exception){ ... }
    }

    Let's say your code calls Log.Message and Log.Error all over the place and you would like to have different logging behaviors - logging to console and to the Debug/Trace listeners. Moreover, you would like to switch logging at runtime based on selected options.

    Why can't we override static members?

    Really, why? If you think about it, this is just common sense. Overriding usual (instance) members uses the virtual dispatch mechanism to separate the contract from the implementation. The contract is known at compile time (instance member signature), but the implementation is only known at runtime (concrete type of object provides a concrete implementation). You don't know the concrete type of the implementation at compile time.

    This is an important thing to understand: when types inherit from other types, they fulfil a common contract, whereas static types are not bound by any contract (from the pure OOP point of view). There's no technical way in the language to tie two static types together with an "inheritance" contract. If you would "override" the Log method in two different places, how do we know which one we are calling here: Log.Message("what is the implementation?")

    With static members, you call them by explicitly specifying the type on which they are defined. Which means, you directly call the implementation, which, again, is not bound to any contract.

    By the way, that's why static members can't implement interfaces. And that's why virtual dispatch is useless here - all clients directly call the implementation, without any contract.

    Let's get back to our problem

    I won't even mention the "solution" with if/switch:

    public static void Message(string message)
    {
        if (LoggingBehavior == LoggingBehavior.Console)
        {
            Console.WriteLine(message);
        }
        else if ...
    }

    It is so ugly that my eyes start bleeding when I stare at it long enough. Why?

    1. You cannot add a new type of a log at runtime
    2. You cannot "override" the functionality at runtime or, say, wrap it in a decorator
    3. You have to modify every static method to add/change/remove a logging behavior
    4. all other 10000 reasons why OOP is better than procedural programming

    But! We are so close to the first solution - every more or less experienced developer will cry out here: use the Strategy pattern!

    Solution 1: Strategy + Singleton

    Yes, that's easy. Define a contract to use and it will be automatically separated from the implementation. (Unless you make it sealed. By making a type or a member sealed, you guarantee that no one else can implement this contract.)

    OK, so here's our contract:

    public abstract class Logger
    {
        public abstract void Message(string message);
        public abstract void Error(Exception exception);
    }

    You could make it an interface as well, but with an interface you wouldn't be able to change the contract later without breaking existing clients. I already wrote about choosing abstract class vs. interface.

    You'll only need one instance of a logging behavior, so let's create a singleton:

    public static class Log
    {
        public static Logger Instance { get; set; }
        ...

    Correct Singleton implementation is not part of this discussion - there is a whole science of how to correctly implement Singleton in .NET - just use your favorite search engine if you're curious.

    Now we just redirect the static methods to instance methods of the Logger instance and presto:

    public static void Message(string message)
    {
        Instance.Message(message);
    }

    And that's it! All of your code continues to use Log.Message and Log.Error, and if you'd like to change the behavior, just say

    Log.Instance = new DebugWriteLineLogger();

    At runtime! You could even wrap the instance using Decorators, watch it using Observers, broadcast using Composite, etc. etc.

    Solution 2: delegates

    When I was saying that static types do not have means to adhere to a contract, I was not quite correct. Delegates are a more fine-granular type of a contract, which regulates methods as opposed to types. Let's define two contracts:

    public delegate void MessageLogger(string message);
    public delegate void ErrorLogger(Exception exception);

    Now let's specify that our Log methods define a contract, not an implementation:

    public static class Log
    {
        public static MessageLogger Message { get; set; }
        public static ErrorLogger Error { get; set; }
    }

    We can still call Log.Message(string), but this time we can substitute an implementation at runtime:

    Log.Message = Debug.WriteLine;

    Also, instead of creating explicit delegates MessageLogger and ErrorLogger, we could reuse the System ones: Action<string> and Action<Exception>, which would work just as well.

    Important update: originally I wrote here that you can convert between different delegate types as long as the method signature is the same (e.g. convert MessageLogger to Action<string> and back). THIS IS WRONG. You can assign delegates of type MessageLogger and Action<string> to point to the same method, but you can't assign them to each other. Delegate type inferencing only works when assigning methods to delegates and not delegates to delegates. I'm sorry about the confusion and thanks to Alex for pointing this out.

    Of course, as with the Singleton from Solution 1, we have to make sure we initialize the default implementation before we call it, otherwise we'll get a null reference exception. We always have to be careful about initializing contracts with the default implementation.

    Now that'd be funny - what if we want to log a null reference exception that Log.Error is null - how in the world are we supposed to do that?

    As you can see, delegates are just as powerful as interfaces when specifying a contract for a single method as opposed to whole type. I already wrote about it too: Delegates as an alternative to single-method interfaces

    A peculiarity with delegates is that you can't switch all the delegates at once, you have to do it one by one. This can be both an advantage and a disadvantage, depending on your situation. Also, there is no clear guidance as to where to put the implementation methods for delegates - they might end up being scattered all over the code.

    Conclusion

    We can use any of the two solutions above to change the logging implementation at runtime, even if the Log class is defined in a different assembly. Moreover, we can switch between the first and the second solution without changing the client source code. We will have to recompile though, because it changes the static methods on Log to static properties and vice versa (breaks binary compatibility, not source level compatibility).

    I'm still not sure whether I like the Singleton or Delegates better. It probably depends on the situation. What do you think?

Page 1 of 1 (2 items)