Добавлен , опубликован
Что такое монада MayBe можно узнать из видео:
Довольно-таки полноценный код монады:
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace System.Monads
{
    /// <summary> Для словарей </summary>
    public static class MaybeIDictionary
    {
        /// <summary> Выполняет действие с каждым элементом словаря, если словарь существует </summary>
        public static IDictionary<TKey, TValue> Do<TKey, TValue>(this IDictionary<TKey, TValue> source,
            Action<TKey, TValue> action)
        {
            if (source != null)
            {
                foreach (var element in source)
                {
                    action(element.Key, element.Value);
                }
            }

            return source;
        }

        /// <summary> Достает значение по указанному ключу, если словарь существует и имеет такой ключ. В противном случае возвращает значение типа по умолчанию. </summary>
        public static TValue With<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
        {
            if (source != null)
            {
                TValue result;
                if (source.TryGetValue(key, out result))
                    return result;
            }
            return default(TValue);
        }

        /// <summary>
        /// Достает значение по указанному ключу, если словарь существует и имеет такой ключ. В противном случае возвращает заданное нами значение по умолчанию </summary>
        public static TValue Return<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue defaultValue)
        {
            if (source != null)
            {
                TValue result;
                if (source.TryGetValue(key, out result))
                    return result;
            }
            return defaultValue;
        }
    }

    /// <summary> Для перечислений - массивы, списки </summary>
    public static class MaybeIEnumerable
    {
        /// <summary> Выполняет указанное действие с каждым элементом списка, если список существует </summary>
        public static IEnumerable Do(this IEnumerable source, Action<object> action)
        {
            var enumerable = source as object[] ?? source.Cast<object>().ToArray();
            if (source != null)
                foreach (var element in enumerable)
                    action(element);
            return enumerable;
        }

        /// <summary> Выполняет указанное действие с каждым элементом списка, если список существует </summary>
        public static IEnumerable<TSource> Do<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
        {
            var enumerable = source as TSource[] ?? source.ToArray();
            if (source != null)
                foreach (var element in enumerable)
                    action(element);
            return enumerable;
        }

        /// <summary> Выполняет указанное действие с каждым элементом списка, если список существует </summary>
        public static IEnumerable<TSource> Do<TSource>(this IEnumerable<TSource> source, Action<TSource, int> action)
        {
            var enumerable = source as TSource[] ?? source.ToArray();
            if (source != null)
                foreach (var element in enumerable.Select((s, i) => new {Source = s, Index = i}))
                    action(element.Source, element.Index);
            return enumerable;
        }

        /// <summary> Проталкивает указанное поле, если список существует. Аналог Select() грубо говоря </summary>
        public static IEnumerable<TResult> With<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> action)
        {
            return source != null ? source.Select(action) : null;
        }
    }
    public static class MaybeNullable
    {
        /// <summary> Выполняет указанное действие, если объект существует. </summary>
        public static TSource? Do<TSource>(this TSource? source, Action<TSource?> action)
            where TSource : struct
        {
            if (source.HasValue)
                action(source);
            return source;
        }

        /// <summary> Проталкивает указанное значение, если объект существует и значение типа по умолчанию, если не существует </summary>
        public static TResult With<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action)
            where TSource : struct
        {
            return source.HasValue ? action(source) : default(TResult);
        }

        /// <summary> Проталкивает указанное значение, если объект существует и значение указанного нами типа, если не существует </summary>
        public static TResult Return<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action, TResult defaultValue)
            where TSource : struct
        {
            return source.HasValue ? action(source) : defaultValue;
        }

        /// <summary> Проталкивает значение дальше, если выполняется условие, в противном случае возвращает значение типа по умолчанию </summary>
        public static TSource? If<TSource>(this TSource? source, Func<TSource?, bool> condition)
            where TSource : struct
        {
            return source.HasValue && condition(source) ? source : default(TSource);
        }

        /// <summary> Пироталкивает значение дальше, если условие не выполняется, в противном случае возвращает значение по умолчанию </summary>
        public static TSource? IfNot<TSource>(this TSource? source, Func<TSource?, bool> condition)
            where TSource : struct
        {
            return source.HasValue && !condition(source) ? source : default(TSource);
        }

        /// <summary> Если объект не существует, восстанавливает его указанным способом </summary>
        public static TInput Recover<TInput>(this TInput? source, Func<TInput> recoverFunc)
            where TInput : struct
        {
            return source.HasValue ? source.Value : recoverFunc();
        }

        /// <summary> Выполняет метод если объект не пуст. Возвращает ошибку, если таковая произошла во время выполнения действия </summary>
        public static Tuple<TSource?, Exception> TryDo<TSource>(this TSource? source, Action<TSource?> action)
            where TSource : struct
        {
            if (source.HasValue)
                try
                {
                    action(source);
                }
                catch (Exception ex)
                {
                    return new Tuple<TSource?, Exception>(source, ex);
                }
            return new Tuple<TSource?, Exception>(source, null);
        }

        /// <summary> Выполняет метод если объект не пуст. Возвращает ошибку, если таковая произошла во время выполнения действия и только если она удовлетворяет условию </summary>
        public static Tuple<TSource?, Exception> TryDo<TSource>(this TSource? source, Action<TSource?> action, Func<Exception, bool> exceptionChecker)
            where TSource : struct
        {
            if (source.HasValue)
                try
                {
                    action(source);
                }
                catch (Exception ex)
                {
                    if (exceptionChecker(ex))
                        return new Tuple<TSource?, Exception>(source, ex);
                    throw;
                }
            return new Tuple<TSource?, Exception>(source, null);
        }

        /// <summary> Выполняет метод если объект не пуст. Возвращает ошибку, если таковая произошла во время выполнения действия и только если она заранее указана в массиве </summary>
        public static Tuple<TSource?, Exception> TryDo<TSource>(this TSource? source, Action<TSource?> action, params Type[] expectedException)
            where TSource : struct
        {
            if (source.HasValue)
                try
                {
                    action(source);
                }
                catch (Exception ex)
                {
                    if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
                        return new Tuple<TSource?, Exception>(source, ex);
                    throw;
                }

            return new Tuple<TSource?, Exception>(source, null);
        }


        /// <summary>
        /// Allows to do some conversion of <paramref name="source"/> if its not null and catch any exceptions
        /// </summary>
        public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action)
            where TSource : struct
        {
            if (source.HasValue)
            {
                TResult result = default(TResult);
                try
                {
                    result = action(source);
                    return new Tuple<TResult, Exception>(result, null);
                }
                catch (Exception ex)
                {
                    return new Tuple<TResult, Exception>(result, ex);
                }
            }

            return new Tuple<TResult, Exception>(default(TResult), null);
        }

        /// <summary>
        /// Allows to do some conversion of <paramref name="source"/> if its not null and catch exceptions, which handled by <param name="exceptionChecker"/>
        /// </summary>
        public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action, Func<Exception, bool> exceptionChecker)
            where TSource : struct
        {
            if (source.HasValue)
            {
                TResult result = default(TResult);
                try
                {
                    result = action(source);
                    return new Tuple<TResult, Exception>(result, null);
                }
                catch (Exception ex)
                {
                    if (exceptionChecker(ex))
                        return new Tuple<TResult, Exception>(result, ex);
                    throw;
                }
            }

            return new Tuple<TResult, Exception>(default(TResult), null);
        }

        /// <summary>
        /// Allows to do some conversion of <paramref name="source"/> if its not null and catch <param name="expectedException"/> exceptions
        /// </summary>
        public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource? source, Func<TSource?, TResult> action, params Type[] expectedException)
            where TSource : struct
        {
            if (source.HasValue)
            {
                TResult result = default(TResult);
                try
                {
                    result = action(source);
                    return new Tuple<TResult, Exception>(result, null);
                }
                catch (Exception ex)
                {
                    if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
                        return new Tuple<TResult, Exception>(result, ex);
                    throw;
                }
            }

            return new Tuple<TResult, Exception>(default(TResult), null);
        }

        /// <summary> Возвращает true если объект не существует </summary>
        public static bool IsNull<TSource>(this TSource? source)
            where TSource : struct
        {
            return source.HasValue == false;
        }

        /// <summary> Возвращает true если объект существует </summary>
        public static bool IsNotNull<TSource>(this TSource? source)
            where TSource : struct
        {
            return source.HasValue;
        }
    }
    public static class MaybeObjects
    {
        /// <summary> Выполняет действие, если объект существует </summary>
        public static TSource Do<TSource>(this TSource source, Action<TSource> action)
            where TSource : class
        {
            if (source != default(TSource))
                action(source);
            return source;
        }

        /// <summary> Проталкивает значение, если объект существует </summary>
        public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
            where TSource : class
        {
            return source != default(TSource) ? action(source) : default(TResult);
        }

        /// <summary> Проталкивает значение, если объект существует, иначе возвращает наше значение по умолчанию </summary>
        public static TResult Return<TSource, TResult>(this TSource source, Func<TSource, TResult> action, TResult defaultValue)
            where TSource : class
        {
            return source != default(TSource) ? action(source) : defaultValue;
        }

        /// <summary> Проталкивает значение дальше, если выполняется условие </summary>
        public static TSource If<TSource>(this TSource source, Func<TSource, bool> condition)
            where TSource : class
        {
            return source != default(TSource) && condition(source) ? source : default(TSource);
        }

        /// <summary> Проталкивает значение дальше, если условие не выполняется</summary>
        public static TSource IfNot<TSource>(this TSource source, Func<TSource, bool> condition)
            where TSource : class
        {
            return source != default(TSource) && !condition(source) ? source : default(TSource);
        }

        /// <summary> Восстанавливает объект указанным способом если он не существует </summary>
        public static TInput Recover<TInput>(this TInput source, Func<TInput> action)
            where TInput : class
        {
            return source ?? action();
        }

        /// <summary> Преобразует объект в указанный тип </summary>
        public static TResult OfType<TResult>(this object source)
        {
            return source is TResult ? (TResult) source : default(TResult);
        }

        /// <summary>
        /// Allows to do <paramref name="action"/> and catch any exceptions
        /// </summary>
        public static Tuple<TSource, Exception> TryDo<TSource>(this TSource source, Action<TSource> action)
            where TSource : class
        {
            if (source != default(TSource))
                try
                {
                    action(source);
                }
                catch (Exception ex)
                {
                    return new Tuple<TSource, Exception>(source, ex);
                }
            return new Tuple<TSource, Exception>(source, null);
        }

        /// <summary>
        /// Allows to do <paramref name="action"/> and catch exceptions, which handled by <param name="exceptionChecker"/>
        /// </summary>
        public static Tuple<TSource, Exception> TryDo<TSource>(this TSource source, Action<TSource> action, Func<Exception, bool> exceptionChecker)
            where TSource : class
        {
            if (source != default(TSource))
                try
                {
                    action(source);
                }
                catch (Exception ex)
                {
                    if (exceptionChecker(ex))
                        return new Tuple<TSource, Exception>(source, ex);
                    throw;
                }
            return new Tuple<TSource, Exception>(source, null);
        }

        /// <summary>
        /// Allows to do <paramref name="action"/> and catch <param name="expectedException"/> exceptions
        /// </summary>
        public static Tuple<TSource, Exception> TryDo<TSource>(this TSource source, Action<TSource> action, params Type[] expectedException)
            where TSource : class
        {
            if (source != default(TSource))
                try
                {
                    action(source);
                }
                catch (Exception ex)
                {
                    if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
                        return new Tuple<TSource, Exception>(source, ex);
                    throw;
                }

            return new Tuple<TSource, Exception>(source, null);
        }


        /// <summary>
        /// Allows to do some conversion of <paramref name="source"/> if its not null and catch any exceptions
        /// </summary>
        public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
            where TSource : class
        {
            if (source != default(TSource))
            {
                TResult result = default(TResult);
                try
                {
                    result = action(source);
                    return new Tuple<TResult, Exception>(result, null);
                }
                catch (Exception ex)
                {
                    return new Tuple<TResult, Exception>(result, ex);
                }
            }

            return new Tuple<TResult, Exception>(default(TResult), null);
        }

        /// <summary>
        /// Allows to do some conversion of <paramref name="source"/> if its not null and catch exceptions, which handled by <param name="exceptionChecker"/>
        /// </summary>
        public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource source, Func<TSource, TResult> action, Func<Exception, bool> exceptionChecker)
            where TSource : class
        {
            if (source != default(TSource))
            {
                TResult result = default(TResult);
                try
                {
                    result = action(source);
                    return new Tuple<TResult, Exception>(result, null);
                }
                catch (Exception ex)
                {
                    if (exceptionChecker(ex))
                        return new Tuple<TResult, Exception>(result, ex);
                    throw;
                }
            }

            return new Tuple<TResult, Exception>(default(TResult), null);
        }

        /// <summary>
        /// Allows to do some conversion of <paramref name="source"/> if its not null and catch <param name="expectedException"/> exceptions
        /// </summary>
        public static Tuple<TResult, Exception> TryWith<TSource, TResult>(this TSource source, Func<TSource, TResult> action, params Type[] expectedException)
            where TSource : class
        {
            if (source != default(TSource))
            {
                TResult result = default(TResult);
                try
                {
                    result = action(source);
                    return new Tuple<TResult, Exception>(result, null);
                }
                catch (Exception ex)
                {
                    if (expectedException.Any(exp => exp.IsInstanceOfType(ex)))
                        return new Tuple<TResult, Exception>(result, ex);
                    throw;
                }
            }

            return new Tuple<TResult, Exception>(default(TResult), null);
        }

        /// <summary>
        /// Handle exception with no actions
        /// </summary>
        public static TSource Catch<TSource>(this Tuple<TSource, Exception> source)
        {
            return source.Item1;
        }

        /// <summary>
        /// Handle exception with <param name="handler"/> action
        /// </summary>
        public static TSource Catch<TSource>(this Tuple<TSource, Exception> source, Action<Exception> handler)
        {
            if (source.Item2 != null)
                handler(source.Item2);
            return source.Item1;
        }

        /// <summary> Возвращает true если объект не существует </summary>
        public static bool IsNull<TSource>(this TSource source)
            where TSource : class
        {
            return source == default(TSource);
        }

        /// <summary> Возвращает true если объект существует </summary>
        public static bool IsNotNull<TSource>(this TSource source)
            where TSource : class
        {
            return source != default(TSource);
        }
    }
}
Исходник взят отсюда, от себя только частично комменты на русском + компоновка в один файлик + пара фиксов кода.
Монада очень важная и очень часто используемая, потому не запостить - грех.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
0
29
10 лет назад
0
Mihahail, обычно прописывают лишь пограничный значения для параметров. Например, метод
GetMessages(int[] messageIds)
Тут 2 пограничных значения:
1 - messageIds = null -> тут должен быть NullArgumentException
2 - messageIds = new int[] {} -> тут должен вернуть пустой список
Это и описывается в тест кейсах
0
20
10 лет назад
0
alexprey, в смысле? Разве GetMessages в описанных случаях не делает так как написано сам по себе?)
Или программист сам не уверен, как работает GetMessages?
0
29
10 лет назад
0
Mihahail, в случае говнокода да :D Суть в том, что когда работает много людей в проекте, все должно работать с примерно одинаковой логикой, как я и описал выше. Конечно но же можно и засунуть туда кейс, что при null возвращает все сообщения, но это весьма не очевидно. Почему именно те два кейса такие, ну например мы юзаем там SqlDataProvider и некороший программист написал его так:
IList<MessageEntity> GetMessages(int[] messageIds) {
   var collection = new EntityCollection<MessageEntity>();
   using(var cmd = new MySqlCommand()) {
       cmd.CommandText = string.Format(@"
SELECT *
FROM messages
WHERE MessageId IN ({0})
        ", messageIds.Join(","));

        FillEntityCollection(cmd, collection);
    }
    return collection;
}
Если у нас параметр null -> NullPointerException словим
Ecли у нас пустой массив -> MySqlException из-за того, что будет выражение вида
WHERE MessageId IN ()
Уже не однократно матерился на работе из-за того, что другие не обрабатывают эти 2 кейса и программа сваливается при проверке. Хотя все ждут что метод вернет пустое множество.
0
27
10 лет назад
0
Кстати для дебага строк на нуль можно добавить вот такой метод:
public static TInput Throw<TInput>(this TInput source, params string[] nullExceptionTexts)
    where TInput : class
{
    if (source == null)
        throw new NullReferenceException(nullExceptionTexts != null
            ? string.Join(Environment.NewLine, nullExceptionTexts)
            : string.Format("Source of type {0} can not be null", typeof (TInput)));
    return source;
}
Соответственно юзать например так
return s_parentType.Throw("SerializeMethod.s_parentType")
      .value.Throw("SerializeMethod.s_parentType.value");
То есть throw фактически выдаст исключение если появится нуль, при том не нужно разбивать всё на несколько строк + можно закинуть кастомные данные для фаст дебага.
0
20
10 лет назад
Отредактирован Mihahail
0
Т.е. юнит тесты, содержащие некоторые проверки нужны, чтобы контроллировать, не забыл ли кто другой из команды программистов реализовать эти проверки в коде?
0
29
10 лет назад
0
Mihahail, да и плюсом, не сломал ли кто-то когда правил/фиксил багу
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.