web-dev-qa-db-ja.com

Option <T>機能タイプの実装とシナリオ

Option<T>関数型を実装したことはありますか?ここで説明します: https://app.pluralsight.com/library/courses/tactical-design-patterns-dot-net-control-flow/table-of-contents

基本的には、C#で潜在的にnull可能なオブジェクト参照の代わりに、要素なしで、または要素を1つだけ使用してIEnumerable<T>を使用することです。 「if(null)」条件がなくなったため、LINQ機能を強化して処理を合理化し、循環的複雑度を低減できます。

私のアイデアの採用は次のようになります。

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();
}

それほど悪くはありません。必要に応じて、Tとの間で自動変換を行う引数/戻り値の型として使用できます。消費の例は次のようになります。

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

次のように定義されている場合、2つのミルクボックス用の単一のバスケットエントリを持つバスケットが作成されます。

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();  
}

どこ:

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; }
}

私はこの一般的な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;
    }
}

ええと、私が見る限り、かなり機能的です。しかし問題は、それは意味をなさないのでしょうか?循環的複雑度は、ShoppingBasketクラスでは非常に低くなっています。ただし、同時に実行する3つの実行の「仮想パス」があると同時に、Place()メソッドの「add」と「replace」の場合も、nullの製品引数をテストする必要があります。

問題は、それらがほとんど見えないことです。あなたは何を言っていますか?個人的には、そのようなコードをC#で維持したいと思いますか?

6
Dmitry Nogin

私が正しく理解しているなら、列挙型として、単一のオブジェクトのインスタンスを含むすべてを表現することについて話していることになります。これに対する私の解決策は、あなたの要素を列挙可能にしてからLinqを使用することです:

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;
}

そうすれば、Linqのようなコードで処理したい要素がある場合、その要素に対して.AsSingleEnumerable()または.AsSingleOrEmptyEnumerable()を呼び出すだけで済みます。

これがあなたが達成しようとしていたことではない場合は、申し訳ありません。知らせてください。

2
Adam Brown