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

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jan 24, 2016 at 14:20
\$\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).

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

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.