web-dev-qa-db-fra.com

Retour facultatif en C # .Net

Java 1.8 reçoit la classe Optional, qui nous permet d'indiquer explicitement quand une méthode peut renvoyer une valeur null et "obliger" son consommateur à vérifier si elle n'est pas null (isPresent()) avant de l'utiliser.

Je vois que C # a Nullable, cela fait quelque chose de similaire, mais avec des types basiques. Il semble être utilisé pour les requêtes de base de données afin de distinguer quand une valeur existe et vaut 0 par rapport à quand elle n'existe pas et est nulle.

Mais il semble que Nullable de C # ne fonctionne pas pour les objets, mais uniquement pour les types de base, alors que celui de Java facultatif ne fonctionne que pour les objets et non pour les types de base.

Existe-t-il une classe Nullable/Optional en C # qui nous oblige à tester si un objet existe avant de l'extraire et de l'utiliser?

35
Hikari

Pas dans la langue, non, mais vous pouvez créer le vôtre:

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

Notez que vous ne pourrez pas émuler certains comportements de Nullable<T>, tels que la possibilité d'encadrer une valeur nullable sans valeur à null, plutôt qu'un encadré nullable, car il dispose d'un support spécial du compilateur pour cela (et quelques autres). comportement.

16
Servy

À mon avis, toute implémentation de Option qui expose la propriété HasValue constitue la défaite de l'idée. L'intérêt des objets facultatifs est que vous pouvez passer des appels inconditionnels à leur contenu sans vérifier si le contenu s'y trouve.

Si vous devez vérifier si l'objet facultatif contient une valeur, vous n'avez rien fait de nouveau par rapport aux tests null courants.

Voici l'article dans lequel j'explique en détail les objets optionnels: Implémentation personnalisée de l'option/Peut-être taper en C #

Et voici le référentiel GitHub avec du code et des exemples: https://github.com/zoran-horvat/option

Si vous êtes réticent à utiliser une solution Option Heavyweight, vous pouvez facilement en construire une légère. Vous pouvez créer votre propre type Option<T> qui implémente l'interface IEnumerable<T>, afin de pouvoir utiliser les méthodes d'extension LINQ pour rendre les appels facultatifs. Voici l'implémentation la plus simple possible:

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

L'utilisation de ce type Option<T> se fait via LINQ:

Option<Car> optional = Option<Car>.Create(myCar);
string color = optional
  .Select(car => car.Color.Name)
  .DefaultIfEmpty("<no car>");

Vous pouvez en savoir plus sur les objets optionnels dans ces articles:

Et vous pouvez vous référer à mes cours vidéo pour plus de détails sur la simplification du flux de contrôle en utilisant le type Option et d'autres moyens: Rendre votre code C # plus fonctionnel et Modèles de conception tactiques dans .NET: Flux de contrôle

Le premier cours vidéo ( Rendre votre code C # plus fonctionnel ) offre une introduction détaillée à la programmation ferroviaire, y compris les types Either et Option et leur utilisation pour gérer des objets optionnels et des cas et erreurs exceptionnels.

18
Zoran Horvat

Il y a une meilleure implémentation du type d'option en C #. Vous pouvez trouver cette implémentation dans Modèles de conception tactiques dans .NET par Zoran Horvat sur pluralsight.com. Il comprend une explication pourquoi et comment l'utiliser. L'idée de base est d'implémenter la classe d'options en tant qu'implémentation de l'interface 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();
    }
}
13
Lukáš Kmoch

Dans le projet "Extensions du langage fonctionnel C #" https://github.com/louthy/language-ext Existe l'objet Option de F # parmi d'autres modèles fonctionnels

3
aloon

C’est peut-être plus proche du type d’option F #

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;
}
2
gileCAD

Il n'y a rien d'intégré, mais vous pouvez définir le vôtre. Notez qu'une implémentation Option<T> n'a pas de sens sans définir les opérateurs 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;
    }
}
2
Lee

Au lieu d'écrire votre propre classe, vous pouvez utiliser Microsoft.FSharp.Core.FSharpOption<T> à partir de l'assembly FSharpCore.dll. Malheureusement, les types F # sont un peu maladroits lorsqu'ils sont utilisés en 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
0
tearvisus

J'ai décidé d'implémenter une sorte de prototype de classe facultatif <> Java il y a quelque temps en utilisant l'une des dernières versions de C #.

Ici c'est en fait:

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

}

0

Existe-t-il une classe Nullable/Optional en C # qui nous oblige à tester si l'objet existe avant de l'extraire et de l'utiliser?

Les nullables ont été créés pour que les types primitifs puissent être nuls. Leur valeur par défaut ne devait pas nécessairement être une valeur réelle (comme int, sans nullables, sa valeur par défaut est 0, donc un 0 signifie-t-il quelque chose de 0 ou une valeur nulle??

Non, vous ne pouvez rien faire pour forcer un programmeur à vérifier si un objet est null. C'est bien quand même. Cela créerait une énorme quantité de frais généraux. S'il s'agissait d'une fonctionnalité linguistique, à quelle fréquence forceriez-vous la vérification? En auriez-vous besoin lorsque la variable est affectée pour la première fois? Et si la variable pointe vers un autre objet plus tard? Le forceriez-vous à vérifier avant chaque méthode et chaque propriété, et si cela échouait, leveriez-vous une exception? Vous obtenez cela maintenant avec une exception de référence null. Vous auriez très peu d'avantages à forcer quelqu'un à faire cela au-delà de ce que vous avez déjà. 

0
kemiller2002