5
\$\begingroup\$

Have you ever being implementing Option<T> functional type? It is discussed here. 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#?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I'm not a fan of your usage example. The line .Place(null) looks really fishy to me. It looks like something, that should not happen in my imaginary web store, like some kind of error, which I probably want to catch and debug. However Optional<T> class hides this potentioal error completely and my application keeps working as if nothing has happened. Now, if null is for some reason a valid value, then I guess its fine.

Overall, Optinal<T> class looks OK to me, even though I am having troubles coming up with a sceanrio, where I would find it useful. You could probably group public methods together though, it makes class easier to read.

Also I think you are overusing => operator. To the point where method's body becomes really hard to read. => is fine when your function is a short one-liner (i.e. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();). But you should probably use braces, when it is not (i.e your Place method).

\$\endgroup\$
1
  • \$\begingroup\$ Just because you can, doesn't mean you should. ++ \$\endgroup\$
    – RubberDuck
    Commented Jan 26, 2016 at 23:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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