C#.Net中的可选返回
Java 1.8正在接收Optional类,它允许我们明确说明方法何时可以返回null值并“强制”其使用者在使用它之前validation它是否为null( isPresent()
)。
我看到C#有Nullable,它做的类似,但基本类型。 它似乎用于数据库查询,以区分值何时存在,并且当它不存在时为0并且为空。
但似乎C#的Nullable不适用于对象,仅适用于基本类型,而Java的Optional仅适用于对象而不适用于基本类型。
在C#中是否有Nullable / Optional类,这迫使我们在提取和使用它之前测试对象是否存在?
不是语言,不,但你可以自己做:
public struct Optional { 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 optional) { return optional.Value; } public static implicit operator Optional (T value) { return new Optional (value); } public override bool Equals(object obj) { if (obj is Optional ) return this.Equals((Optional )obj); else return false; } public bool Equals(Optional other) { if (HasValue && other.HasValue) return object.Equals(value, other.value); else return HasValue == other.HasValue; } }
请注意,您将无法模拟Nullable
某些行为,例如将没有值的可空值设置为null的能力,而不是盒装的可空,因为它具有特殊的编译器支持(并且一些其他的)行为。
在我看来,暴露HasValue
属性的任何Option
实现都是整个想法的失败。 可选对象的要点是您可以无条件地调用其内容,而无需测试内容是否存在。
如果必须测试可选对象是否包含值,那么与常见的null
测试相比,您没有做任何新的事情。
这篇文章中我详细解释了可选对象: 自定义实现选项/可能在C#中输入
这里是带有代码和示例的GitHub存储库: https : //github.com/zoran-horvat/option
如果您不愿意使用重量级的Option解决方案,那么您可以轻松构建轻量级的解决方案。 您可以创建自己的Option
类型来实现IEnumerable
接口,这样您就可以利用LINQ扩展方法来调用可选的调用。 这是最简单的实现:
public class Option : IEnumerable { private readonly T[] data; private Option(T[] data) { this.data = data; } public static Option Create(T value) { return new Option (new T[] { value }); } public static Option CreateEmpty() { return new Option (new T[0]); } public IEnumerator GetEnumerator() { return ((IEnumerable )this.data).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.data.GetEnumerator(); } }
使用此Option
类型通过LINQ完成:
Option optional = Option .Create(myCar); string color = optional .Select(car => car.Color.Name) .DefaultIfEmpty("");
您可以在这些文章中找到有关可选对象的更多信息
- 在C#中自定义实现Option / Maybe类型
- 理解选项(可能)function类型
- 如何降低圈复杂度:期权function类型
您可以参考我的video课程,了解有关如何使用Option
类型和其他方法简化控制流程的更多详细信息: 在.NET中 制作C#代码更多function和战术设计模式:控制流程
第一个video课程( 让您的C#代码更具function性 )详细介绍了面向铁路的编程,包括Either
和Option
类型,以及它们如何用于管理可选对象和处理特殊情况和错误。
在C#中有更好的选项类型实现。 您可以通过pluralsight.com上的Zoran Horvat在.NET中的战术设计模式中找到这种实现方式 。 它包括解释为什么以及如何使用它。 基本思想是将选项类实现为IEnumerable <>接口的实现。
public class Option : IEnumerable { private readonly T[] data; private Option(T[] data) { this.data = data; } public static Option Create(T element) { return new Option (new[] { element }); } public static Option CreateEmpty() { return new Option (new T[0]); } public IEnumerator GetEnumerator() { return ((IEnumerable ) this.data).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } }
在项目“C#function语言扩展” https://github.com/louthy/language-ext中存在F#的Option对象以及其他function模式
内置任何东西,但您可以定义自己的内置。 请注意,如果没有定义map / bind运算符, Option
实现就没有意义。
public struct Option { 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 Select (Func selector) { return this.hasValue ? new Option (selector(this.value)) : new Option (); } public Option SelectMany (Func> bind) { return this.hasValue ? bind(this.value) : new Option (); } public bool HasValue { get { return this.hasValue; } } public T GetOr(T @default) { return this.hasValue ? this.value : @default; } }
也许这更接近F#Option类型
public struct Option { 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 None => new Option (); public static Option Some(T value) => new Option (value); private Option(T value) { this.value = value; hasValue = true; } public TResult Match(Func someFunc, Func noneFunc) => hasValue ? someFunc(value) : noneFunc(); public override bool Equals(object obj) { if (obj is Option) { var opt = (Option )obj; return hasValue ? opt.IsSome && opt.Value.Equals(value) : opt.IsNone; } return false; } public override int GetHashCode() => hasValue ? value.GetHashCode() : 0; }
在C#中是否有Nullable / Optional类,这迫使我们在提取和使用它之前测试对象是否存在?
创建了Nullables,以便原始类型可以为null。 它们的默认值不一定是实际值(如int,没有nullables它的默认值是0,那么0表示0或不设置为0?)
没有什么可以强制程序员检查对象是否为空。 那很好。 这样做会产生巨大的开销。 如果这是一种语言function,您多久会强行检查一次? 首次分配变量时是否需要它? 如果变量稍后指向另一个对象怎么办? 你会强制它在每个方法和属性之前检查,如果失败你会抛出exception吗? 你现在可以使用null引用exception。 除了你已经拥有的东西之外,强迫某人做这件事你会得到很少的好处。
您可以使用FSharpCore.dll
程序FSharpCore.dll
Microsoft.FSharp.Core.FSharpOption
,而不是编写自己的类。 不幸的是,在C#中使用时,F#类型有点笨拙。
//Create var none = FSharpOption.None; var some1 = FSharpOption .Some("some1"); var some2 = new FSharpOption ("some2"); //Does it have value? var isNone1 = FSharpOption .get_IsNone(none); var isNone2 = OptionModule.IsNone(none); var isNone3 = FSharpOption .GetTag(none) == FSharpOption .Tags.None; var isSome1 = FSharpOption .get_IsSome(some1); var isSome2 = OptionModule.IsSome(some1); var isSome3 = FSharpOption .GetTag(some2) == FSharpOption .Tags.Some; //Access value var value1 = some1.Value; //NullReferenceException when None var value2 = OptionModule.GetValue(some1); //ArgumentException when None
我决定前一段时间使用最后一个C#版本实现某种Optional <> Java类原型。
事实上这是:
public sealed class Optional { private static readonly Optional EMPTY = new Optional (); private readonly T value; private Optional() => value = default; private Optional(T arg) => value = arg.RequireNonNull("Value should be presented"); public static Optional Empty() => EMPTY; public static Optional Of(T arg) => new Optional (arg); public static Optional OfNullable(T arg) => arg != null ? Of(arg) : Empty(); public static Optional OfNullable(Func outputArg) => outputArg != null ? Of(outputArg()) : Empty(); public bool HasValue => value != null; public void ForValuePresented(Action action) => action.RequireNonNull()(value); public IOption Where(Predicate predicate) => HasValue ? predicate.RequireNonNull()(value) ? this : Empty() : this; public IOption Select (Func select) => HasValue ? Optional .OfNullable(select.RequireNonNull()(value)) : Optional .Empty(); public IOption> SelectMany (Func> select) => HasValue ? Optional>.OfNullable(select.RequireNonNull()(value)) : Optional>.Empty(); public T Get() => value; public T GetCustomized(Func getCustomized) => getCustomized.RequireNonNull()(value); public U GetCustomized(Func getCustomized) => getCustomized.RequireNonNull()(value); public T OrElse(T other) => HasValue ? value : other; public T OrElseGet(Func getOther) => HasValue ? value : getOther(); public T OrElseThrow(Func exceptionSupplier) where E : Exception => HasValue ? value : throw exceptionSupplier(); public static explicit operator T(Optional optional) => OfNullable((T) optional).Get(); public static implicit operator Optional (T optional) => OfNullable(optional); public override bool Equals(object obj) { if (obj is Optional ) return true; if (!(obj is Optional )) return false; return Equals(value, (obj as Optional ).value); } public override int GetHashCode() => base.GetHashCode(); public override string ToString() => HasValue ? $"Optional has <{value}>" : $"Optional has no any value: <{value}>";
}