Як я б створив метод TryParse, який надає детальну інформацію у випадку помилки розбору?


9

Під час розбору вводу користувача, як правило, рекомендується не кидати та виловлювати винятки, а скоріше використовувати методи перевірки. У .NET BCL це буде різниця між, наприклад, int.Parse(кидає виняток щодо недійсних даних) та int.TryParse(поверненням falseнедійсних даних).

Я проектую своє

Foo.TryParse(string s, out Foo result)

метод, і я не впевнений у поверненому значенні. Я міг би використовувати boolтакий власний TryParseметод .NET , але це не дало б ніяких вказівок про тип помилки, про точну причину, через s яку не можна було розібратися в Foo. (Наприклад, sможуть бути незрівнянні дужки, або неправильна кількість символів, або Barбез відповідного Bazтощо)

Як користувач API, я сильно не люблю методів, які просто повертають булевий успіх / провал, не розказуючи мені, чому операція не вдалася. Це робить налагодження гри вгадуванням, і я не хочу нав'язувати це клієнтам моєї бібліотеки.

Я можу придумати багато вирішень цієї проблеми (повернення кодів статусу, повернення рядка помилки, додавання рядка помилки як вихідний параметр), але всі вони мають свої відповідні мінуси, і я також хочу дотримуватися конвенцій .NET Framework .

Отже, моє запитання таке:

Чи є у .NET Framework методи, які (a) аналізують введення без викидів винятків та (b) все ще повертають більш детальну інформацію про помилку, ніж простий істинний / хибний булевий?


1
Це посилання не робить висновок, що не рекомендується викидати та ловити винятки. Бувають випадки, коли найкращий спосіб використовувати Parse().
папараццо

Відповіді:


5

Я рекомендую використовувати шаблон монади для вашого типу повернення.

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

Зауважте також, що Foo не повинен нести відповідальність за роз'яснення того, як його слід аналізувати з введення користувача, оскільки це безпосередньо пов'язує ваш доменний шар із вашим шаром інтерфейсу користувача, а також порушує єдину головну відповідальність.

Ви можете також зробити клас результатів розбору, специфічний для Fooзамість використання дженерики, залежно від випадку використання.

Специфічний клас результату розбору для foo може виглядати приблизно так:

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

Ось версія Monad:

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

Я не знаю жодних методів у .net рамках, які повертають детальну інформацію про помилки розбору.


Я розумію ваш коментар щодо прив'язки шару інтерфейсу, але в цьому випадку існує стандартизоване, канонічне рядкове представлення Foo, тому має сенс мати Foo.ToStringі Foo.Parse.
Хайнзі

І, wrt моє жирне запитання, чи можете ви надати приклад із .NET BCL, який використовує цю модель?
Хайнзі

4
Як це монада?
ЖакБ

@Heinzi: Будь-який метод, який повертає a Func<T>, відповідав би цим критеріям, якщо ви включаєте Tпотрібну інформацію. Повернення детальної інформації про помилку багато в чому залежить від вас. Ви думали про використання Maybe<T>? Дивіться mikhail.io/2016/01/monads-explained-in-csharp
Роберт Харві

@JacquesB: Мені якось цікаво було те саме. Підпис методу сумісний з моданічною поведінкою, але це стосується цього.
Роберт Харві

1

Ви можете подивитися на ModelState в рамках MVC. Він представляє спробу розбору деякого вводу і може мати набір помилок.

З цього приводу, я не думаю, що для цього .net BCL існує повторювана закономірність, оскільки винятки - в кращому чи гіршому випадку - встановлена ​​схема для звітування про умови помилок у .net. Я думаю, вам слід просто продовжити і реалізувати власне рішення, що відповідає вашій проблемі, наприклад, ParseResultклас з двома підкласами, SuccessfulParseі FailedParse, де SuccessfulParseмає властивість з проаналізованим значенням і FailedParseмає властивість повідомлення про помилку. Поєднувати це з відповідним малюнком у C # 7 може бути досить елегантно.


1

У мене виникли подібні проблеми з тим, щоб хотіти використовувати TryParse/Convert/etc.метод, де мені іноді потрібно знати, як і чому він не вдався.

Нарешті я взяв натхнення від того, як деякі серіалізатори обробляють помилки та використовують події. Таким чином синтаксис мого TryX(..., out T)методу виглядає таким же чистим, як і будь-який інший, і надійно повертає простий, falseяк випливає з малюнка.

Однак, коли мені потрібно більше деталей, я просто додаю обробник подій і отримую всі необхідні результати в пакеті такий складний чи простий, як я хочу ( MyEventArgsнижче). Додайте його до списку рядків, додайте ExceptionDispatchInfoта захоплюйте Винятки; нехай той, хто телефонує, вирішить, чи хоче він боротися з тим, що йде не так.

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;}
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.