C# – Option functional type implementation and scenarios

cdesign-patternsobject-oriented-designvalue-object

Have you ever being implementing Option<T> functional type? It is discussed here:
https://app.pluralsight.com/library/courses/tactical-design-patterns-dot-net-control-flow/table-of-contents

Basically it is about using IEnumerable<T> with no or only one element instead of potentially nullable object reference in C#. We can empower LINQ functionality to streamline processing reducing cyclomatic complexity because of no "if(null)" conditions anymore.

My adoption of the idea looks this way:

public struct Optional<T> : IEnumerable<T>
    where T : class
{
    public static implicit operator Optional<T>(T value)
    {
        return new Optional<T>(value);
    }

    public static implicit operator T(Optional<T> optional)
    {
        return optional.Value;
    }

    Optional(T value)
        : this()
    {
        Value = value;
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (HasValue)
            yield return Value;
    }

    T Value { get; }
    bool HasValue => Value != null;
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    public override string ToString() => Value?.ToString();
}

It is not so bad, we can use it as argument/return type with automatic conversion to/from T when necessary. Consumption example might look like this:

     var p = new Product("Milk");
     var basket = ShoppingBasket.Empty
            .Place(p)
            .Place(p)
            .Place(null);

It creates basket with single basket entry for two milk boxes if the following is defined:

public static class ShoppingBasket
{
    public static readonly IEnumerable<BasketItem> Empty =
        Enumerable.Empty<BasketItem>();

    public static IEnumerable<BasketItem> Place(
        this IEnumerable<BasketItem> basket, 
        Optional<Product> product) =>
            basket
                .SetOrAdd(
                    i => product.Contains(i.Product), 
                    i => i.OneMore(), 
                    product.Select(p => new BasketItem(p, 1)))
                .ToArray();  
}

Where:

public class BasketItem
{
    public BasketItem(Product product, int quantity)
    {
        Product = product;
        Quantity = quantity;
    }

    public Product Product { get; }
    public int Quantity { get; }
    public BasketItem OneMore() => 
        new BasketItem(Product, Quantity + 1);
}

public class Product
{
    public Product(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

I use this general IEnumerable<T> extension:

public static class EnumerableHelper
{
    public static IEnumerable<T> SetOrAdd<T>(
        this IEnumerable<T> source,
        Func<T, bool> predicate,
        Func<T, T> set,
        params T[] add)
    {
        return source
            .SetOrAdd(predicate, set, add as IEnumerable<T>);
    } 

    public static IEnumerable<T> SetOrAdd<T>(
        this IEnumerable<T> source,
        Func<T, bool> predicate,
        Func<T, T> set,
        IEnumerable<T> add)
    {
        var empty = Enumerable.Empty<T>();
        foreach (var item in source)
            if (predicate(item))
            {
                yield return set(item);
                add = empty;
            }
            else
                yield return item;

        foreach (var item in add)
            yield return item;
    }
}

Well, it is pretty functional as far as I can see. But the question is: does it make any sense? Cyclomatic complexity is very low in ShoppingBasket class. But the same time we still have three "virtual paths" of execution to test – for "add" and "replace" in the Place() method + we also need to test for null product argument.

The problem is that they are almost invisible. What do you say? Would you personally prefer to maintain such kind of code in C#?

Best Answer

If I'm understanding correctly, you're talking about expressing everything, including single object instaces, as enumerations. My solution to this is just to make your element an enumerable and then use Linq, like so:

public static IEnumerable<T> AsSingleOrEmptyEnumerable<T>(this T element)
    where T: class
{
        if (Equals(element, default(T)))
            return Enumerable.Empty<T>();
        return element.AsSingleEnumerable();
}

private static IEnumerable<T> AsSingleEnumerable<T>(this T element)
{
    yield return element;
}

That way, when you have some element you want to process with Linq-like code, you just call .AsSingleEnumerable() or .AsSingleOrEmptyEnumerable() on it and off you go.

I apologize if this isn't what you were trying to achieve - let me know.

Related Topic