9

When parsing user input, it is generally recommended to not to throw and catch exceptions but rather to use validation methods. In the .NET BCL, this would be the difference between, for example, int.Parse (throws an exception on invalid data) and int.TryParse (returns false on invalid data).

I am designing my own

Foo.TryParse(string s, out Foo result)

method and I'm unsure about the return value. I could use bool like .NET's own TryParse method, but that would give no indication about the type of error, about the exact reason why s could not be parsed into a Foo. (For example, s could have unmatched parenthesis, or the wrong number of characters, or a Bar without a corresponding Baz, etc.)

As a user of APIs, I strongly dislike methods which just return a success/failure Boolean without telling me why the operation failed. This makes debugging a guessing game, and I don't want to impose that on my library's clients either.

I can think of a lot of workarounds to this issue (return status codes, return an error string, add an error string as an out parameter), but they all have their respective downsides, and I also want to stay consistent with the conventions of the .NET Framework.

Thus, my question is as follows:

Are there methods in the .NET Framework which (a) parse input without throwing exceptions and (b) still return more detailed error information than a simple true/false Boolean?

  • 1
    That link does not conclude it is not recommended to throw and catch exceptions. There are times that the best way is to use Parse(). – paparazzo Jan 23 '18 at 21:23
5

I would recommend using the monad pattern for your return type.

ParseResult<Foo> foo = FooParser.Parse("input");

Note also, it should not be Foo's responsibility to figure out how it should be parsed from user input as this directly binds your domain layer to your UI layer, and also violates the single responsibility principal.

You can also make a parse result class specific to Foo instead of using generics, depending on your use case.

A foo specific parse result class might look something like this:

class FooParseResult
{
     Foo Value { get; set; }
     bool PassedRequirement1 { get; set; }
     bool PassedRequirement2 { get; set; }
}

Here is the Monad version:

class ParseResult<T>
{
     T Value { get; set; }
     string ParseErrorMessage { get; set; }
     bool WasSuccessful { get; set; }
}

I am not aware of any methods in the .net framework that return detailed parse error information.

  • I understand your comment about UI layer binding, but in this case there exists a standardized, canonical string representation of Foo, so it makes sense to have Foo.ToString and Foo.Parse. – Heinzi Jan 22 '18 at 15:41
  • And, w.r.t. my bolded question, can you give me an example from the .NET BCL that uses this pattern? – Heinzi Jan 22 '18 at 15:46
  • 3
    How is that a monad? – JacquesB Jan 22 '18 at 15:49
  • @Heinzi: Any method that returns a Func<T> would meet that criteria, if you include in T the information you need. Returning detailed error information is largely up to you. Have you considered using a Maybe<T>? See mikhail.io/2016/01/monads-explained-in-csharp – Robert Harvey Jan 22 '18 at 15:49
  • @JacquesB: I was kinda wondering the same thing. The method signature is compatible with modanic behavior, but that's about it. – Robert Harvey Jan 22 '18 at 15:50
1

You could look at ModelState in the MVC framework. It represent an attempted parse of some input and may have a collection of errors.

That said, I don't think there is a recurring pattern for this in the .net BCL, since exceptions is - for better or worse - the established pattern for reporting error conditions in .net. I think you should just go ahead and implement your own solution suiting your problem, for example a ParseResult class with two subclasses, SuccessfulParse and FailedParse, where SuccessfulParse has a property with the parsed value and FailedParse has an error message property. Combining this with pattern matching in C# 7 could be pretty elegant.

1

I've run into similar problems with wanting to use a TryParse/Convert/etc. method where I sometimes need to know how and why it failed.

I ended up taking inspiration from how some serializers handle errors and using events. This way the syntax for my TryX(..., out T) method looks as clean as any other and reliably returns a simple false as the pattern implies.

However, when I want need more details I just add an Event Handler and get whatever results I need in a package as complex or simple as I want (the MyEventArgs below). Add it to a list of strings, add an ExceptionDispatchInfo and capture Exceptions; let the caller decide if and how it wants to deal with anything that goes wrong.

public class Program
{
    public static void Main()
    {
        var c = new MyConverter();

        //here's where I'm subscibing to errors that occur
        c.Error += (sender, args) => Console.WriteLine(args.Details);

        c.TryCast<int>("5", out int i);
    }
}

//here's our converter class
public class MyConverter
{
    //invoke this event whenever something goes wrong and fill out your EventArgs with details
    public event EventHandler<MyEventArgs> Error;

    //intentionally stupid implementation
    public bool TryCast<T>(object input, out T output)
    {
        bool success = true;
        output = default (T);

        //try-catch here because it's an easy way to demonstrate my example
        try
        {
            output = (T)input;
        }
        catch (Exception ex)
        {
            success = false;
            Error?.Invoke(this, new MyEventArgs{Details = ex.ToString()});
        }

        return success;
    }
}

//stores whatever information you want to make available
public class MyEventArgs : EventArgs
{
    public string Details {get; set;}
}

protected by gnat May 20 at 21:24

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.