Template for raising errors / warnings when parsing in the library

I have a library that has two input formats for the object model described in the library. I am currently using the event subscription model to raise errors / warnings / verbose messages for the end user of the library. This was not the purest model, and I was wondering if there was an appropriate design template or something similar in the .NET Framework (when in Rome) to better deal with 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 no longer works quickly for all fatal errors using an exception. Messages are written to potentially multiple sources at the end of the user, so any template should avoid limiting the number of potential sources.

+4
source share
6 answers

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

  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 can still add an event if the calling library object wants events on the fly.

+3
source

It seems you need a composite validator in which your users can connect to their own logic to designate specific non-fatal errors as fatal, warning or informational messages. Throwing exceptions does not match the bill, because you already left this method if the other part wanted to turn it into a warning, but wanted to continue parsing. This sounds the same as the two exception skipping models for skipping in Windows to handle structured exceptions. It basically looks like

  • Register as many exception handlers as you like
  • When a problem is detected in the passage, all the handlers are set (until the exploit is thrown!), Which must be processed with an error. The first, which says yes, will become the actual handler that decides what to do.
  • When a handler that can handle it was found, we get 2 and call it. This time this is the exception - throw 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 set, but you can still continue parsing when you are gone. Not a single state has yet been destroyed.

In C # you, of course, have much more freedom to turn this into a more flexible error conversion and report pipeline, where you can convert for a strict reader, for example. all error warnings.

Which route you need depends on how your library is used, and how qualified the users of your library are and how many service requests you want to handle, as some users make dumb mistakes. There is always a trade-off between beeing as strict as possible (perhaps too strict and you get a complicated library) or too relaxed (a pretty parser, but silently skips half of my input due to internal errors).

The Windows SDK libraries are sometimes quite difficult to use because the engineers there optimize more for less ringing calls. They throw a Win32 or HResult error code at you, and you must find out which principle (memory alignment, buffer size, cross-threading ...) you violated.

+2
source

I think the event subscription mode is fine. But instead, you can consider the interface. This can give you more flexibility. Something like that:

 public interface IMessageHandler { void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs); } public abstract class ModelReader : IDisposable { private readonly IMessageHandler handler; // Should be initialized somewhere, eg 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 the message handler to display your event subscription:

 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); } } } 
+1
source

Your current approach is not the cleanest model because it has two conflicting execution styles, pull (reading input) and push (handling error callbacks), and this means that both your reader and his clients require more government administration to providing a coherent whole.

I recommend that you avoid the XmlReader interface and use the visitor template to enter input and errors into the client application.

+1
source

If there is an error that prevents the library from working, I would use an exception.

If this is strictly for tracing and tooling, I would either go with your template, or with the TextWriter template, where the consumer would go to a text writer and you would write tracing information, the only problem with this is that you can only have one external subscriber. but the result is slightly cleaner code.

  public TextWriter Log { get; set; } private void WriteToLog(string Message) { if (Log != null) Log.WriteLine(message); } 
0
source

I know that you mentioned that you can have multiple subscribers, so event handlers and calling the Injected interface are good solutions, as mentioned above. For completeness, I also suggest providing an additional Read () parameter as Func. eg:

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

Then, of course, you could do whatever you want in the Func delegate, for example, display a warning from several sources. But in combination with the Event model, it allows to process all warnings in a general form (for example, a log) and use Func for separate specialized actions and control flow (if necessary).

0
source

All Articles