web-dev-qa-db-ja.com

C#.Netでのオプションの戻り値

Java 1.8はOptionalクラスを受け取ります。これにより、メソッドがnull値を返す可能性があることを明示的に宣言し、コンシューマを「強制」してnullでないことを確認できます(isPresent())。

C#にはNullableがあり、似たようなことをしますが、基本型があります。 DBクエリに使用され、値が存在して0である場合と存在しない場合およびnullである場合を区別するために使用されるようです。

しかし、C#のNullableはオブジェクトに対しては機能せず、基本タイプに対してのみ機能するように見えますが、JavaのOptionalはオブジェクトに対してのみ機能し、基本タイプに対しては機能しないようです。

オブジェクトを抽出して使用する前に、オブジェクトが存在するかどうかをテストすることを強制するC#にNullable/Optionalクラスがありますか?

42
Hikari

言語ではなく、いいえ、しかしあなたはあなた自身を作ることができます:

public struct Optional<T>
{
    public bool HasValue { get; private set; }
    private T value;
    public T Value
    {
        get
        {
            if (HasValue)
                return value;
            else
                throw new InvalidOperationException();
        }
    }

    public Optional(T value)
    {
        this.value = value;
        HasValue = true;
    }

    public static explicit operator T(Optional<T> optional)
    {
        return optional.Value;
    }
    public static implicit operator Optional<T>(T value)
    {
        return new Optional<T>(value);
    }

    public override bool Equals(object obj)
    {
        if (obj is Optional<T>)
            return this.Equals((Optional<T>)obj);
        else
            return false;
    }
    public bool Equals(Optional<T> other)
    {
        if (HasValue && other.HasValue)
            return object.Equals(value, other.value);
        else
            return HasValue == other.HasValue;
    }
}

Nullable<T>の特定の動作をエミュレートすることができないことに注意してください。たとえば、ボックス化されたnullableではなく、nullに値のないnullable値をボックス化する機能は、他のいくつかの)動作。

19
Servy

私の意見では、Optionプロパティを公開するHasValue実装はすべてのアイデアの敗北です。オプションのオブジェクトのポイントは、コンテンツがそこにあるかどうかをテストせずに、そのコンテンツを無条件に呼び出すことができるということです。

オプションのオブジェクトに値が含まれているかどうかをテストする必要がある場合、一般的なnullテストと比較して新しいことは何もしていません。

以下に、オプションオブジェクトを詳細に説明している記事を示します。 C#でのオプション/多分型のカスタム実装

そして、ここにコードとサンプルを含むGitHubリポジトリがあります: https://github.com/zoran-horvat/option

重いオプションソリューションを使用したくない場合は、簡単に軽量なソリューションを構築できます。 Option<T>インターフェイスを実装する独自のIEnumerable<T>型を作成して、LINQ拡張メソッドを利用して呼び出しをオプションにすることができます。可能な限り単純な実装を次に示します。

public class Option<T> : IEnumerable<T>
{
    private readonly T[] data;

    private Option(T[] data)
    {
        this.data = data;
    }

    public static Option<T> Create(T value)
    {
        return new Option<T>(new T[] { value });
    }

    public static Option<T> CreateEmpty()
    {
        return new Option<T>(new T[0]);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)this.data).GetEnumerator();
    }

    System.Collections.IEnumerator
        System.Collections.IEnumerable.GetEnumerator()
    {
        return this.data.GetEnumerator();
    }
}

このOption<T>タイプの使用は、LINQを介して行われます。

Option<Car> optional = Option<Car>.Create(myCar);
string color = optional
  .Select(car => car.Color.Name)
  .DefaultIfEmpty("<no car>")
  .Single();  // you can call First(), too

オプションのオブジェクトの詳細については、次の記事をご覧ください。

また、Optionタイプやその他の方法を使用して制御フローを簡素化する方法の詳細については、ビデオコースを参照できます。 C#コードをより機能的にする および 戦術的なデザインパターン.NETの場合:制御フロー

最初のビデオコース( C#コードをより機能的にする )では、EitherおよびOptionタイプやその使用方法など、鉄道指向プログラミングの詳細な紹介を行います。オプションのオブジェクトを管理し、例外的なケースとエラーを処理します。

26
Zoran Horvat

C#にはオプションタイプのより良い実装があります。この実装は、 。NETの戦術設計パターン で、orthoussight.comのZoran Horvatによって見つけることができます。理由と使用方法の説明が含まれています。基本的な考え方は、IEnumerable <>インターフェイスの実装としてオプションクラスを実装することです。

public class Option<T> : IEnumerable<T>
{
    private readonly T[] data;

    private Option(T[] data)
    {
        this.data = data;
    }

    public static Option<T> Create(T element)
    {
        return new Option<T>(new[] { element });
    }

    public static Option<T> CreateEmpty()
    {
        return new Option<T>(new T[0]);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>) this.data).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}
13
Lukáš Kmoch

プロジェクト「C#機能言語拡張機能」 https://github.com/louthy/language-ext には、他の機能パターンの中でもF#のOptionオブジェクトが存在します。

3
aloon

組み込みのものはありませんが、独自に定義できます。 Option<T>実装は、マップ/バインド演算子を定義しないと意味がありません。

public struct Option<T>
{
    private bool hasValue;
    private T value;

    public Option(T value)
    {
        if (value == null) throw new ArgumentNullException("value");
        this.hasValue = true;
        this.value = value;
    }

    public Option<TOut> Select<TOut>(Func<T, TOut> selector)
    {
        return this.hasValue ? new Option<TOut>(selector(this.value)) : new Option<TOut>();
    }

    public Option<TOut> SelectMany<TOut>(Func<T, Option<TOut>> bind)
    {
        return this.hasValue ? bind(this.value) : new Option<TOut>();
    }

    public bool HasValue
    {
        get { return this.hasValue; }
    }

    public T GetOr(T @default)
    {
        return this.hasValue ? this.value : @default;
    }
}
3
Lee

おそらくこれはF#オプションタイプに近いでしょう

public struct Option<T>
{
    private T value;
    private readonly bool hasValue;

    public bool IsSome => hasValue;

    public bool IsNone => !hasValue;

    public T Value
    {
        get
        {
            if (!hasValue) throw new NullReferenceException();
            return value;
        }
    }

    public static Option<T> None => new Option<T>();

    public static Option<T> Some(T value) => new Option<T>(value);

    private Option(T value)
    {
        this.value = value;
        hasValue = true;
    }

    public TResult Match<TResult>(Func<T, TResult> someFunc, Func<TResult> noneFunc) =>
        hasValue ? someFunc(value) : noneFunc();

    public override bool Equals(object obj)
    {
        if (obj is Option<T>)
        {
            var opt = (Option<T>)obj;
            return hasValue ? opt.IsSome && opt.Value.Equals(value) : opt.IsNone;
        }
        return false;
    }

    public override int GetHashCode() =>
        hasValue ? value.GetHashCode() : 0;
}
2
gileCAD

Zoran Horvatの答えから多くを学びました。これが私のコードです。 optionalは、実際の値または空にすることができます。消費側では、同じコードがすべてを処理します。

void Main()
{
    var myCar = new Car{ Color =  Color.Black, Make="Toyota"};

    Option<Car> optional = Option<Car>.Create(myCar);

    // optional is an Empty 50% of the time.
    if(new Random().NextDouble() > 0.5)
        optional = Option<Car>.CreateEmpty();



    string color = optional
    .Select(car => car.Color.Name)
    .DefaultIfEmpty("<no car>")
    .Single();
    Console.Write(color);
}

class Car {
    public Color Color { get; set; }
    public string Make { get; set;}
}

public class Option<T> : IEnumerable<T>
{
    private readonly T[] data;

    private Option(T[] data)
    {
        this.data = data;
    }

    public static Option<T> Create(T value)
    {
        return new Option<T>(new T[] { value });
    }

    public static Option<T> CreateEmpty()
    {
        return new Option<T>(new T[0]);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)this.data).GetEnumerator();
    }

    System.Collections.IEnumerator
        System.Collections.IEnumerable.GetEnumerator()
    {
        return this.data.GetEnumerator();
    }
}
0
Rm558

独自のクラスを作成する代わりに、Microsoft.FSharp.Core.FSharpOption<T>アセンブリのFSharpCore.dllを使用できます。残念ながら、C#で使用する場合、F#型は少し扱いに​​くいです。

//Create
var none = FSharpOption<string>.None;
var some1 = FSharpOption<string>.Some("some1");
var some2 = new FSharpOption<string>("some2");

//Does it have value?
var isNone1 = FSharpOption<string>.get_IsNone(none);
var isNone2 = OptionModule.IsNone(none);
var isNone3 = FSharpOption<string>.GetTag(none) == FSharpOption<string>.Tags.None;

var isSome1 = FSharpOption<string>.get_IsSome(some1);
var isSome2 = OptionModule.IsSome(some1);
var isSome3 = FSharpOption<string>.GetTag(some2) == FSharpOption<string>.Tags.Some;

//Access value
var value1 = some1.Value; //NullReferenceException when None
var value2 = OptionModule.GetValue(some1); //ArgumentException when None
0
tearvisus

ある種のOptional <> Javaクラスプロトタイプを最後のC#バージョンの1つを使用して実装することにしました。

ここに実際にあります:

public sealed class Optional<T>
{
    private static readonly Optional<T> EMPTY = new Optional<T>();
    private readonly T value;

    private Optional() => value = default;
    private Optional(T arg) => value = arg.RequireNonNull("Value should be presented");

    public static Optional<T> Empty() => EMPTY;
    public static Optional<T> Of(T arg) => new Optional<T>(arg);
    public static Optional<T> OfNullable(T arg) => arg != null ? Of(arg) : Empty();
    public static Optional<T> OfNullable(Func<T> outputArg) => outputArg != null ? Of(outputArg()) : Empty();

    public bool HasValue => value != null;

    public void ForValuePresented(Action<T> action) => action.RequireNonNull()(value);

    public IOption<T> Where(Predicate<T> predicate) => HasValue 
        ? predicate.RequireNonNull()(value) ? this : Empty() : this;

    public IOption<TOut> Select<TOut>(Func<T, TOut> select) => HasValue 
        ? Optional<TOut>.OfNullable(select.RequireNonNull()(value)) 
        : Optional<TOut>.Empty();

    public IOption<IOption<TOut>> SelectMany<TOut>(Func<T, IOption<TOut>> select) => HasValue 
        ? Optional<IOption<TOut>>.OfNullable(select.RequireNonNull()(value)) 
        : Optional<IOption<TOut>>.Empty();

    public T Get() => value;
    public T GetCustomized(Func<T, T> getCustomized) => getCustomized.RequireNonNull()(value);
    public U GetCustomized<U>(Func<T, U> getCustomized) => getCustomized.RequireNonNull()(value);

    public T OrElse(T other) => HasValue ? value : other;
    public T OrElseGet(Func<T> getOther) => HasValue ? value : getOther();
    public T OrElseThrow<E>(Func<E> exceptionSupplier) where E : Exception => HasValue ? value : throw exceptionSupplier();

    public static explicit operator T(Optional<T> optional) => OfNullable((T) optional).Get();
    public static implicit operator Optional<T>(T optional) => OfNullable(optional);

    public override bool Equals(object obj)
    {
        if (obj is Optional<T>) return true;
        if (!(obj is Optional<T>)) return false;
        return Equals(value, (obj as Optional<T>).value);
    }

    public override int GetHashCode() => base.GetHashCode();
    public override string ToString() => HasValue ? $"Optional has <{value}>" : $"Optional has no any value: <{value}>";

}

0

オブジェクトを抽出して使用する前にオブジェクトが存在するかどうかをテストすることを強制するC#にNullable/Optionalクラスがありますか?

Nullableは、プリミティブ型がnullになるように作成されました。それらのデフォルト値は実際の値である必要はありませんでした(intのように、null許容値なしではデフォルトは0です。0は何か0を意味するのか、それとも0に設定されていないのですか?)

いいえ、オブジェクトがnullかどうかをプログラマに確認させるためにできることは何もありません。それは良いことです。そうすると、莫大なオーバーヘッドが発生します。これが言語機能である場合、どのくらいの頻度でチェックを強制しますか?変数が最初に割り当てられるときにそれを必要としますか?変数が後で別のオブジェクトを指す場合はどうなりますか?すべてのメソッドとプロパティの前にチェックするように強制し、それが失敗した場合は例外をスローしますか?これで、null参照例外が発生します。既に持っている以上のことを誰かに強制することで得られる利益はほとんどありません。

0
kemiller2002