[英]Optional return in C#.Net
Java 1.8 正在接收Optional<T>
類,它允許我們明確說明方法何時可以返回空值並“強制”其使用者在使用它之前驗證它是否不為空( isPresent()
)。
我看到 C# 有Nullable<T>
,它做了類似的事情,但具有基本類型。 它似乎用於數據庫查詢,以區分值何時存在且為 0,何時不存在且為空。
但似乎 C# 的Nullable<T>
不適用於對象,僅適用於基本類型,而 Java 的Optional<T>
僅適用於對象,不適用於基本類型。
C# 中是否有 Nullable/Optional 類,它迫使我們在提取和使用對象之前測試對象是否存在?
不是在語言中,不,但您可以自己制作:
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>
某些行為,例如將沒有值的可空值裝箱為空的能力,而不是裝箱的可空值,因為它對此具有特殊的編譯器支持(以及其他一些)行為。
在我看來,任何公開HasValue
屬性的Option
實現都是對整個想法的失敗。 可選對象的要點是您可以無條件調用它們的內容,而無需測試內容是否存在。
如果您必須測試可選對象是否包含值,那么與常見的null
測試相比,您沒有做任何新的事情。
這是我詳細解釋可選對象的文章: C# 中選項/可能類型的自定義實現
這是包含代碼和示例的 GitHub 存儲庫: https : //github.com/zoran-horvat/option
如果您不願意使用重量級的 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
類型以及如何使用它們來管理可選對象以及處理異常情況和錯誤。
C# 中有更好的選項類型實現。 您可以在Zoran Horvat 的.NET中的Tactical design patterns in .NET 中找到此實現,網址為 Multiplesight.com。 它包括解釋為什么以及如何使用它。 基本思想是將選項類實現為 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();
}
}
沒有內置的東西,但你可以定義你自己的。 請注意,如果不定義映射/綁定運算符,則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;
}
}
在“C# 函數式語言擴展”項目https://github.com/louthy/language-ext 中存在 F# 的 Option 對象以及其他函數式模式
您可以使用FSharpCore.dll
程序FSharpCore.dll
Microsoft.FSharp.Core.FSharpOption<T>
,而不是編寫自己的類。 不幸的是,F# 類型在 C# 中使用時有點笨拙。
//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
也許這更接近於 F# Option 類型
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;
}
前段時間我決定使用最新的 C# 版本之一來實現某種 Optional<> Java 類原型。
這里是:
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}>";
}
C# 中是否有 Nullable/Optional 類,它迫使我們在提取和使用對象之前測試對象是否存在?
創建 Nullables 以便原始類型可以為 null。 它們的默認值不必是實際值(像 int 一樣,如果沒有可空值,它的默認值為 0,那么 0 是指 0 還是未設置為 0?)
不,您無法強制程序員檢查對象是否為空。 不過這很好。 這樣做會產生大量的開銷。 如果這是一項語言功能,您多久會強制檢查一次? 首次分配變量時是否需要它? 如果變量稍后指向另一個對象怎么辦? 你會強制它在每個方法和屬性之前檢查,如果失敗你會拋出異常嗎? 你現在得到一個空引用異常。 強迫某人做超出你已有的事情,你不會得到什么好處。
從 Zoran Horvat 的回答中學到了很多。 這是我的代碼。 可選可以有一個真實的值或一個空的。 在消費方面,相同的代碼處理它們。
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();
}
}
使用T?
可為空引用而不是Option<T>
從 C#8 開始,您應該棄用自定義Option<T>
實現。 零困境現已完全解決。
T?
是對Option<T>
的完全替代
C# 具有以下處理 null 的特性:
請記住
Option<Car> optional = Option<Car>.Create(myCar);
string color = optional
.Select(car => car.Color.Name)
.DefaultIfEmpty("<no car>")
.Single(); // you can call First(), too
是相同的
string color = myCar?.Color.Name ?? "<no car>";
此外,字符串顏色也是一個不能為空的引用。
如果您不喜歡烘焙自己的解決方案,我會使用Language Ext 。 它在 nuget 上可用。 我最近開始使用這個庫,空引用的安全性是驚人的! 我不是這個庫的專家,但它可以滿足您的要求,等等。
以下是可以做的事情:
using System;
using LanguageExt;
using static LanguageExt.Prelude;
public class Demo
{
public static Option<int> ToEvenOrNone(int i) =>
i % 2 == 0
? i.Apply(Optional)
: None;
public static void PrintToDebug(Option<int> value) =>
value
.Some(Console.WriteLine)
.None(() => Console.WriteLine("Value is not Even!"));
public static void Test()
{
for (int i = 0; i < 10; i++)
{
PrintToDebug(ToEvenOrNone(i));
}
}
}
這是輸出:
0
Value is not Even!
2
Value is not Even!
4
Value is not Even!
6
Value is not Even!
8
Value is not Even!
https://github.com/mcintyre321/OneOf
我認為這個 oneOf 類很好地重新創建了選項類型。 它甚至包括一個 .switch/.match 與模式匹配,最重要的是它在運行時工作,這是你對 Option 模式的期望。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.