4

I've got a library which has two input formats for the object model described by the library. I currently use an event subscription model for raising errors/warnings/verbose messages to the end user of the library. This hasn't proven to be the cleanest model, and I was wondering if there was a relevant design pattern or something similar found in the .Net Framework (when in Rome) to better handle this situation.

// Rough outline of the current code
public abstract class ModelReader : IDisposable
{
    public abstract Model Read();

    public event EventHandler<MessageAvailableEventArgs> MessageAvailable;

    protected virtual void RaiseError(string message)
    {
        var handler = this.MessageAvailable;
        if (handler != null)
        {
            handler(this, new MessageAvailaibleEventArgs(
                TraceEventType.Error, message);
        }
    }
}

Edit: some clarification. The Read routine will already fail fast on all Fatal errors using an exception. The messages are being logged to potentially multiple sources on the user's end, thus any pattern should avoid limiting the number of potential sources.

user7116
  • 63,008
  • 17
  • 141
  • 172

6 Answers6

3

I can give you a real world example. The Html Agility Pack library is a parsing library. It simply defines a list of parsing errors on the the reader class. Extending your example, it would be something like:

    public abstract class ModelReader
    {
        private List<ParseError> _errors = new List<ParseError>();
        private bool _throwOnError;

        public ModelReader()
           :this(true)
        {
        }

        public ModelReader(bool throwOnError)
        {
           _throwOnError = throwOnError;
        }

        // use AddError in implementation when an error is detected
        public abstract Model Read();

        public virtual IEnumerable<ParseError> Errors
        {
           get {return _errors;}
        }

        protected virtual void AddError(ParseError error)
        {
           if (_throwOnError) // fail fast?
              throw new ParseException(error);

           _errors.Add(error);
        }
    }

    public class ParseError
    {
        public ParseError(...)
        {
        }

        public ParseErrorCode Code { get; private set; }
        public int Line { get; private set; }
        public int LinePosition { get; private set; }
        public string Reason { get; private set; }
        public string SourceText { get; private set; }
        public int StreamPosition { get; private set; }
    }

    public enum ParseErrorCode
    {
       InvalidSyntax,
       ClosingQuoteNotFound,
       ... whatever...
    }

    public class ParseException: Exception
    {
        ...
    }

And you still can add an event if the library caller wants on-the-fly events.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Hey. I'd appreciate you adding a disclaimer to answers in which you promote the HTML Agility Pack noting that you are its author. I see there are a bunch of these in which you don't currently disclose your affiliation - at http://stackoverflow.com/a/4601681/1709587, http://stackoverflow.com/a/5229972/1709587, http://stackoverflow.com/a/5441683/1709587, http://stackoverflow.com/a/13126335/1709587, http://stackoverflow.com/a/5145916/1709587 and here. – Mark Amery Dec 05 '14 at 23:34
  • I'm not promoting anything here. You can see my "affiliation" as you say if you click on me as a user. – Simon Mourier Dec 06 '14 at 08:09
  • Hmm, sorry - you're right that this particular post isn't actually recommending using Html Agility Pack, just mentioning it casually. I should perhaps have read more carefully before commenting. My request stands for all the linked posts, though. – Mark Amery Dec 06 '14 at 10:51
2

You seem to want a composable validator where your users can plug in their own logic to flag specific non fatal errors as either fatal, warning or informational messages. Throwing an exception does not fit the bill since you have left the method already if another part wanted to transform it into a warning but wanted to continue parsing. This sounds much like the two pass exception handling model in Windows for Structured Exception Handling. It basically goes like

  1. Register as many exception handlers as you wish
  2. When a problem is detected in pass all handlers are asked (no exeption throwing yet!) which one wants to handle the error. The first one which says yes will become the actual handler which decides what to do.
  3. When a handler was found that can handle we come to pass 2 and call it. This time it is exception throwing time or not. It is entirely up to the handler.

The beauty of this two pass model is that during the first pass all handlers are asked but you still are able to continue parsing where you left. No state has been destroyed yet.

In C# you have of course much more freedom to turn this into a more flexible error transformation and reporting pipeline where you can transform for a strict reader e.g. all warnings to an error.

Which route you need to go depends on how your library is beeing used and how skilled the users of your library are and how many service requests you want to handle because some users make dumb errors. There is always a tradeoff between beeing as strict as possible (possibly too strict and you get a hard to use library) or too relaxed (nice parser but skips silently half of my input due to internal errors).

The Windows SDK Libraries are sometimes pretty hard to use because the engineers there optimize more towards less serivce calls. They throw a Win32 error code or HResult at you and you have to figure out which principle (memory alignment, buffer size, cross threading, ...) you have violated.

Alois Kraus
  • 13,229
  • 1
  • 38
  • 64
1

I think event subscription mode is OK. But you can consider interface instead. This might give you more flexibility. Something like this:

public interface IMessageHandler
{
    void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs);
}    

public abstract class ModelReader : IDisposable
{
    private readonly IMessageHandler handler; // Should be initialized somewhere, e.g. in constructor

    public abstract Model Read();

    public event EventHandler<MessageAvailableEventArgs> MessageAvailable;

    protected virtual void RaiseError(string message)
    {
        MessageAvailaibleEventArgs eventArgs =
            new MessageAvailaibleEventArgs(TraceEventType.Error, message);
        this.handler.HandleMessage(this, eventArgs);
    }
}

So now you can use any implementation of message handler, for exmaple your event subscription one:

public class EventMessageHandler : IMessageHandler
{
    public event EventHandler<MessageAvailaibleEventArgs> MessageAvailable;

    public void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs)
    {
        var handler = this.MessageAvailable;
        if (handler != null)
        {
            handler(this, new MessageAvailaibleEventArgs(
                TraceEventType.Error, message);
        }
    }
}
Andrew Bezzub
  • 15,744
  • 7
  • 51
  • 73
  • Interesting, still potentially limits me to one sink. – user7116 Oct 26 '10 at 13:54
  • Hi @sixlettervariables! bear in mind that you could compose instances of IMessageHandler together to get your desired behavior :) Is that what you meant? – nick2083 Sep 30 '11 at 13:44
1

Your current approach isn't the cleanest model because it has two contradictory execution styles, pull (reading the input) and push (handling error callbacks), and that means both your reader and its clients require more state management to provide a coherent whole.

I recommend you avoid the XmlReader-like interface, and use the visitor pattern to push the input data and errors to the client application.

Huperniketes
  • 940
  • 7
  • 17
  • As in I supply a `MessageVisitor` when invoking the `ModelReader`? Seems about the same as an event handler in practice for my application. The model is described in a 50-60k line ASCII file and includes about 25-30 high level entities. There isn't really any way to decouple the reading of the ASCII model description into individual entities either to use say a `ModelPartVisitor` approach. – user7116 Sep 29 '11 at 13:57
  • Event handlers are a generalized form of visitor, where the structure is linear. When you read each entity from the 'ModelReader', don't you use some logic to branch to different entity processors? They aren't suitable candidates for methods to be invoked by the `ModelReader`? I don't understand what you see as the obstacle in improving your model. The goal, at least initially, should be to just move both reading and error-handling to a uniform model (whether push or pull). – Huperniketes Sep 29 '11 at 22:05
  • The problem is the entities aren't built up in one pass, and usually require 2-3 passes to be constructed properly. – user7116 Sep 29 '11 at 23:34
  • I still don't see how that's a problem. Suppose your current model has a client with a processing loop where it calls the `ModelReader` to extract each element of interest from the file and then do whatever processing is necessary to integrate it into the entities it's constructing. All you're doing is moving that processing loop into the `ModelReader` to drive the client's processing. Alternatively, you can change the code that raises exceptions to instead place error codes into the input stream for the reader to return to its client, and let the client choose how to handle errors. – Huperniketes Sep 29 '11 at 23:58
  • Certainly would simplify some aspects. A lot of the current structure is to ensure errors detected are accurately placed on the line (column) they occur, even if it is a logical error (graph cycle when disallowed, etc). – user7116 Sep 30 '11 at 02:12
0

If there are error that stop the library from doing it's work I would use exception.

If this is strictly for tracing and instrumentation I would either go with your pattern or a TextWriter pattern, where the consumer would pass in a text writer and you would write your trace info to that, the only problem with that is you can only have one external subscriber. but it results in a slightly cleaner code.

 public TextWriter Log { get; set; }

 private void WriteToLog(string Message)
 {
    if (Log != null) Log.WriteLine(message);
 }
kay.one
  • 7,622
  • 6
  • 55
  • 74
0

I know you mentioned that you may have several subscribers, so Event handlers and an Injected interface call are both good solutions as already mentioned above. For completeness I will also offer providing an optional Read() parameter as a Func. for example:

    void Read(Func<string, bool> WarningHandler)
    {
        bool cancel = false;
        if (HasAWarning)
            cancel = WarningHandler("Warning!");
    }

Then of course you could do whatever you wanted in the Func delegate, like divy the warning out to several sources. But when coupled with the Event model allows you to handle all Warnings in a general way (like a logger perhaps), and use the Func for individual specialized actions and control flow (if necessary).

deepee1
  • 12,878
  • 4
  • 30
  • 43