Hai mai implementato Option<T>
di tipo funzionale? È discusso qui:
link
Fondamentalmente si tratta di usare IEnumerable<T>
con no o solo un elemento invece di riferimento a oggetti potenzialmente nullable in C #. Possiamo potenziare la funzionalità LINQ per semplificare l'elaborazione riducendo la complessità ciclomatica a causa della mancanza di condizioni "if (null)".
La mia adozione dell'idea sembra in questo modo:
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();
}
Non è così male, possiamo usarlo come argomento / tipo di ritorno con conversione automatica da / a T quando necessario. L'esempio del consumo potrebbe essere simile a questo:
var p = new Product("Milk");
var basket = ShoppingBasket.Empty
.Place(p)
.Place(p)
.Place(null);
Crea un cestino con una singola entrata nel carrello per due scatole del latte se è definito quanto segue:
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();
}
Dove:
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; }
}
Uso questa estensione generica IEnumerable<T>
:
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;
}
}
Bene, è abbastanza funzionale per quanto posso vedere. Ma la domanda è: ha senso? La complessità ciclomatica è molto bassa nella classe ShoppingBasket. Ma allo stesso tempo abbiamo ancora tre "percorsi virtuali" di esecuzione da testare - per "aggiungere" e "sostituire" nel metodo Place () + abbiamo anche bisogno di testare l'argomento del prodotto nullo.
Il problema è che sono quasi invisibili. Che ne dici? Preferiresti personalmente mantenere questo tipo di codice in C #?