Delegate-based Visitors

subtercosm 0 Tallied Votes 132 Views Share

C# delegates make it really easy to implement the visitor pattern.

Instead of making an abstract class and Visit methods that accept a single argument of that class's type, let your Visit methods accept multiple arguments. So instead of a visitor class with N abstract methods, your Visit methods take N arguments, delegates that correspond to the respective methods.

The following example shows the class Result<T> that would contain the result of a computation. If the result is successful, a value of type T would be returned, inside of a Win<T>. If the result is unsuccessful, a Fail<T> would be returned with a message.

This example doesn't show methods that return values of the type Result<T> or how such a value would be used -- it just demonstrates the mechanics of the delegate-based visitor pattern.

The implementation for Result.ValueOrDefault is much nicer than the reflection-based approach using casts and the 'is' operator.

public abstract class Result<T> {
    public abstract U Visit<U>(
        Func<Win<T>, U> onWin,
        Func<Fail<T>, U> onFail
    );

    public override ToString() { return Result.ConvertToString(this); }
}

public class Win<T> : Result<T> {
    public T Value { get; private set; }
    public Win(T value) { Value = value; }

    public override U Visit<U>(Func<Win<T>, U> onWin,
                               Func<Fail<T>, U> onFail) {
        return onWin(this);
    }
}

public class Fail<T> : Result<T> {
    public string Message { get; private set; }
    public Fail(string message) { Message = message; }

    public override U Visit<U>(Func<Win<T>, U> onWin,
                               Func<Fail<T>, U> onFail) {
        return onFail(this);
    }
}

public static class Result {
    public static bool IsWin<T>(this Result<T> result) {
        return result.Visit(win => true, fail => false);
    }
    public static T ValueOrDefault<T>(this Result<T> result,
                                      T defaultValue) {
        return result.Visit(win => win.Value, fail => defaultValue);
    }
    public static string ConvertToString<T>(Result<T> result) {
        return result.Visit(win => "Win: " + win.Value.ToString(),
                            fail => "Fail: " + fail.Message);
    }
}