7

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#?

3
  • 7
    This question might be a better fit for Code Review Jan 24, 2016 at 14:02
  • There's a C# implementation of the Option<T> type in the book Real World Functional Programming With Examples in F# and C# by Tomas Petricek. I've used it and it works quite well. See Chapter 6. Jan 24, 2016 at 14:17
  • 1
    The thing I like about this implementation is it's more canonically accurate to what the Optional type is. The C# implementation of Option<T> I use requires extension methods to make use of the LINQ methods. LINQ functionality comes out-of-the-box with this implementation.
    – JSarwer
    Dec 18, 2019 at 21:53

1 Answer 1

2

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.

1
  • Yep, something like that. Optional<T> though allows Optional<string> Foo(string? bar) => bar;, which is a little bit more dense I think. It also delivers result plurality to expect by the function signature return type. Apr 6, 2020 at 17:52

Your Answer

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

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