警告の理由については、「ヌル値可能な参照型の試用」のセクションThe issue with T?
で 説明しています。要するに、使用する場合、型がクラスか構造体かを指定する必要があります。ケースごとに2つのタイプを作成することになります。T?
より深い問題は、1つのタイプを使用してResultを実装し、SuccessとErrorの両方の値を保持すると、Resultで修正されるはずだった同じ問題と、さらにいくつかの問題が発生することです。
- 同じ型は、型またはエラーのいずれかでデッド値を運ぶか、またはnullを戻す必要があります
- タイプのパターンマッチングはできません。これを機能させるには、いくつかの凝った位置パターンマッチング式を使用する必要があります。
- nullを回避するには、F#のオプションと同様にOption / Maybeのようなものを使用する必要があります。ただし、値またはエラーのどちらでも、Noneを保持します。
F#の結果(およびいずれか)
開始点は、F#のResult型と識別された共用体でなければなりません。結局のところ、これはすでに.NETで機能しています。
F#の結果タイプは次のとおりです。
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
タイプ自体は、必要なものだけを保持します。
F#のDUを使用すると、nullを必要とせずに完全なパターンマッチングが可能になります。
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
C#8でこれをエミュレートする
残念ながら、C#8にはまだDUがなく、C#9でスケジュールされています。C#8ではこれをエミュレートできますが、完全なマッチングは失われます。
#nullable enable
public interface IResult<TResult,TError>{}
struct Success<TResult,TError> : IResult<TResult,TError>
{
public TResult Value {get;}
public Success(TResult value)=>Value=value;
public void Deconstruct(out TResult value)=>value=Value;
}
struct Error<TResult,TError> : IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error)=>ErrorValue=error;
public void Deconstruct(out TError error)=>error=ErrorValue;
}
そしてそれを使う:
IResult<double,string> Sqrt(IResult<double,string> input)
{
return input switch {
Error<double,string> e => e,
Success<double,string> (var v) when v<0 => new Error<double,string>("Negative"),
Success<double,string> (var v) => new Success<double,string>(Math.Sqrt(v)),
_ => throw new ArgumentException()
};
}
完全なパターンマッチングを使用しない場合は、コンパイラの警告を回避するためにデフォルトの句を追加する必要があります。
オプションであっても、死んだ値を導入せずに完全に一致させる方法を探しています。
オプション/たぶん
完全一致を使用する方法でOptionクラスを作成する方が簡単です。
readonly struct Option<T>
{
public readonly T Value {get;}
public readonly bool IsSome {get;}
public readonly bool IsNone =>!IsSome;
public Option(T value)=>(Value,IsSome)=(value,true);
public void Deconstruct(out T value,out bool isSome)=>(value,isSome)=(Value,IsSome);
}
//Convenience methods, similar to F#'s Option module
static class Option
{
public static Option<T> Some<T>(T value)=>new Option<T>(value);
public static Option<T> None<T>()=>default;
}
で使用できるもの:
string cateGory = someValue switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name
};