Подтвердить что ты не робот

Необязательный возврат в С#.Net

Java 1.8 получает необязательный класс, который позволяет нам явно говорить, когда метод может возвращать нулевое значение и "принудительно" его потребителя, чтобы проверить, не является ли он не null (isPresent()), прежде чем использовать его.

Я вижу, что на С# есть Nullable, что-то похожее, но с базовыми типами. Кажется, он используется для запросов БД, чтобы различать, когда существует значение, и является 0 из того, когда оно не существует и имеет значение null.

Но похоже, что С# Nullable не работает для объектов, только для базовых типов, тогда как Java Option работает только для объектов, а не для базовых типов.

Есть ли в С# класс Nullable/Optional, который заставляет нас проверять, существует ли объект до извлечения и использования?

4b9b3361

Ответ 1

Нет на языке, нет, но вы можете сделать свой собственный:

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>, такие как возможность присваивать значение с нулевым значением без значения null, а не с нулевым значением в боксе, поскольку для него есть специальная поддержка компилятора ( и другое) поведение.

Ответ 2

На мой взгляд, любая реализация Option которая предоставляет свойство HasValue является поражением всей идеи. Суть необязательных объектов заключается в том, что вы можете совершать безусловные обращения к их содержимому, не проверяя, существует ли содержимое.

Если вам нужно проверить, содержит ли необязательный объект значение, то вы ничего нового не сделали по сравнению с обычными null тестами.

Вот статья, в которой я подробно объясняю необязательные объекты: Пользовательская реализация Option/Maybe Type в С#

А вот репозиторий 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 и других средств: сделать код С# более функциональным и тактическим шаблоном проектирования в .NET: поток управления

Первый видеокурс (Повышение функциональности вашего кода С#) знакомит с программированием, ориентированным на железную дорогу, включая типы Either и Option и их использование для управления необязательными объектами и обработки исключительных случаев и ошибок.

Ответ 3

В С# улучшена реализация типа опции. Вы можете найти это воплощение в Тактические шаблоны проектирования в .NET от Zoran Horvat на сайте multipalsight.com. Он включает объяснение, почему и как его использовать. Основная идея заключается в реализации класса option в качестве реализации интерфейса 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();
    }
}

Ответ 4

В проекте "Расширения функционального языка С#" https://github.com/louthy/language-ext существует объект Option F # среди других функциональных паттернов

Ответ 5

Нет ничего встроенного, но вы можете определить свой собственный. Обратите внимание, что реализация 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;
    }
}

Ответ 6

Возможно, это ближе к типу 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;
}

Ответ 7

Есть ли в С# класс Nullable/Optional, который заставляет нас проверить, если объект существует до извлечения и использования?

Неверные объекты были созданы так, чтобы примитивные типы могли быть нулевыми. Их значение по умолчанию не должно быть фактическим значением (например, int, без значений NULL по умолчанию равно 0, так что 0 означает что-то 0 или не установлено ни на что 0?)

Нет ничего, что можно сделать, чтобы заставить программиста проверить, является ли объект нулевым. Это хорошо. Это создаст огромное количество накладных расходов. Если это была языковая особенность, как часто вы будете принудительно проверять? Вам понадобится это, когда переменная будет сначала назначена? Что делать, если переменная указывает на другой объект позже? Вы заставите его проверять перед каждым методом и свойством, и если он не удастся, вы выбросите исключение? Вы получаете это сейчас с помощью исключения с нулевой ссылкой. Вам будет очень мало пользы в том, чтобы заставить кого-то сделать это за пределами того, что у вас уже есть.

Ответ 8

Вместо написания собственного класса вы можете использовать Microsoft.FSharp.Core.FSharpOption<T> из сборки FSharpCore.dll. К сожалению, типы 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

Ответ 9

Я решил каким-то образом создать прототип опционального <> 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}>";

}

Ответ 10

Многое узнал из ответа Зорана Хорват. Вот мой код необязательный может иметь реальное значение или пустой. С другой стороны, один и тот же код обрабатывает их все.

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();
    }
}