68

Java 1.8 正在接收这个Optional<T>类,它允许我们明确说明一个方法何时可以返回一个 null 值并“强制”它的使用者在使用它之前验证它是否不是 null ( isPresent())。

我看到 C# has Nullable<T>,它做了类似的事情,但具有基本类型。它似乎用于数据库查询,以区分值何时存在且为 0 与不存在且为 null 时。

但似乎 C#Nullable<T>不适用于对象,仅适用于基本类型,而 JavaOptional<T>仅适用于对象,不适用于基本类型。

C# 中是否有一个 Nullable/Optional 类,它迫使我们在提取和使用对象之前测试它是否存在?

4

13 回答 13

37

不是语言,不,但您可以自己制作:

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>,例如将没有值的可空值装箱为空的能力,而不是装箱的可空值,因为它具有对该(和其他一些)行为的特殊编译器支持.

于 2013-04-24T18:14:48.173 回答
35

在我看来,任何Option暴露HasValue属性的实现都是整个想法的失败。可选对象的要点是您可以无条件地调用它们的内容,而无需测试内容是否存在。

如果您必须测试可选对象是否包含值,那么与普通null测试相比,您没有做任何新的事情。

这是我详细解释可选对象的文章:C# 中 Option/Maybe 类型的自定义实现

这是包含代码和示例的 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# 代码更具功能性)详细介绍了面向铁路的编程,包括EitherOption类型以及如何使用它们来管理可选对象和处理异常情况和错误。

于 2016-09-25T18:42:23.123 回答
13

在 C# 中有更好的选项类型实现。您可以在 Pluralsight.com 上的 Zoran Horvat的 .NET 中的战术设计模式中找到这个实现。它包括解释为什么以及如何使用它。基本思想是将选项类实现为 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();
    }
}
于 2016-03-26T11:33:15.597 回答
3

没有内置的,但您可以定义自己的。请注意,如果Option<T>不定义 map/bind 操作符,实现是没有意义的。

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;
    }
}
于 2013-04-24T18:32:32.330 回答
3

在项目“C# 功能语言扩展” https://github.com/louthy/language-ext 中存在 F# 的 Option 对象以及其他功能模式

于 2016-09-25T18:27:00.947 回答
3

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
于 2016-12-05T10:58:22.713 回答
3

也许这更接近 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;
}
于 2016-12-09T16:16:08.043 回答
1

不久前,我决定使用最后一个 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}>";
}
于 2018-05-07T08:25:27.183 回答
0

C# 中是否有一个 Nullable/Optional 类,它迫使我们在提取和使用对象之前测试它是否存在?

创建 Nullables 以便原始类型可以为 null。它们的默认值不必是实际值(如 int,没有可空值,它的默认值为 0,那么 0 是指 0 还是未设置为 0?)

不,您无法强制程序员检查对象是否为空。不过那很好。这样做会产生巨大的开销。如果这是一项语言功能,您会多久强制检查一次?首次分配变量时是否需要它?如果变量稍后指向另一个对象怎么办?你会强迫它在每个方法和属性之前检查,如果它失败了你会抛出异常吗?你现在得到了一个空引用异常。强迫别人做你已经拥有的东西,你会得到很少的好处。

于 2013-04-24T18:22:05.123 回答
0

从 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();
    }
}
于 2019-04-04T21:10:47.703 回答
0

使用T?可为空的引用而不是Option<T>

从 C#8 开始,您应该弃用自定义Option<T>实现。现在完全解决了无效的困境。

T?是完全替代Option<T>

C# 具有以下处理 null 的功能:

  1. 空合并运算符
  2. 空条件运算符
  3. 不可为空和可以为空的引用类型(C#8 起)
  4. 可配置的编译错误/警告

请记住,

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

此外,字符串颜色也是一个不能为空的引用。

于 2020-02-19T15:23:43.253 回答
0

如果您不喜欢烘焙自己的解决方案,我会使用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!
于 2020-06-12T10:50:27.497 回答
0

https://github.com/mcintyre321/OneOf

我认为这个 oneOf 类很好地重新创建了选项类型。它甚至包括一个带有模式匹配的 .switch/.match ,最重要的是它在运行时工作,这是您对 Option 模式的期望。

于 2021-02-24T15:58:15.917 回答