Mauvais usage de peut-être monade et les méthodes d'extension dans C#?
edition 2015 à Cette question et ses réponses ne sont plus pertinents. Il a été demandé avant L'avènement de C# 6, qui a l'opérateur de propagation nul (?.), ce qui permet d'éviter les solutions de fortune discutées dans cette question et dans les réponses subséquentes. À partir de 2015, dans C# vous devez maintenant utiliser le formulaire.ActiveForm?.ActiveControl?.Nom.
j'ai réfléchi au problème de propagation nulle dans. NET, ce qui conduit souvent à un code laid et répété comme celui-ci:
tentative # 1 code habituel:
string activeControlName = null;
var activeForm = Form.ActiveForm;
if (activeForm != null)
{
var activeControl = activeForm.ActiveControl;
if(activeControl != null)
{
activeControlname = activeControl.Name;
}
}
il y a eu quelques discussions sur StackOverflow au sujet D'un monad peut-être, ou en utilisant une sorte de méthode d'extension "if not null":
essai n ° 2, Méthode d'extension:
// Usage:
var activeControlName = Form.ActiveForm
.IfNotNull(form => form.ActiveControl)
.IfNotNull(control => control.Name);
// Definition:
public static TReturn IfNotNull<TReturn, T>(T instance, Func<T, TReturn> getter)
where T : class
{
if (instance != null ) return getter(instance);
return null;
}
je pense que c'est mieux, cependant, il y a un peu de désordre syntaxique avec le "IfNotNull" répété et les lambdas. Je considère maintenant cette conception:
tentative #3, peut-être avec la méthode d'extension
// Usage:
var activeControlName = (from window in Form.ActiveForm.Maybe()
from control in window.ActiveControl.Maybe()
select control.Name).FirstOrDefault();
// Definition:
public struct Maybe<T> : IEnumerable<T>
where T : class
{
private readonly T instance;
public Maybe(T instance)
{
this.instance = instance;
}
public T Value
{
get { return instance; }
}
public IEnumerator<T> GetEnumerator()
{
return Enumerable.Repeat(instance, instance == null ? 0 : 1).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public static class MaybeExtensions
{
public static Maybe<T> Maybe<T>(this T instance)
where T : class
{
return new Maybe<T>(instance);
}
}
ma question Est : est-ce un mauvais abus des méthodes d'extension? C'est mieux que les vieux chèques nuls?
8 réponses
il est intéressant que tant de gens choisissent indépendamment le nom IfNotNull
, pour cela en C# - il doit être le nom le plus raisonnable possible! :)
le plus ancien que j'ai trouvé sur SO: pièges possibles de l'utilisation de cette (méthode d'extension basée) sténographie
Mon seul (dans l'ignorance de ce qui précède): Pipe avant en C#
un Autre exemple plus récent: Comment cherchez des NULL dans une expression lambda profonde?
il y a quelques raisons pour lesquelles la méthode d'extension IfNotNull
peut être impopulaire.
-
certaines personnes sont catégoriques qu'une méthode d'extension devrait jeter une exception si son paramètre
this
estnull
. Je ne suis pas d'accord si le nom de la méthode est clair. -
les prolongations qui s'appliquent de façon trop large auront tendance à encombrer la liste d'autocomplétion. Cela peut être évité par l'utilisation correcte des espaces de noms afin qu'ils ne gênent pas les gens qui ne veulent pas d'eux, cependant.
j'ai joué avec l'approche IEnumerable
aussi, juste comme une expérience pour voir combien de choses je pourrais déformer pour correspondre aux mots-clés Linq, mais je pense que le résultat final est moins lisible que soit le IfNotNull
enchaînement ou le code impératif brut.
j'ai fini avec un classe simple et autonome Maybe
avec une méthode statique (pas une méthode d'extension) et qui fonctionne très bien pour moi. Mais ensuite, je travaille avec une petite équipe, et mon prochain collègue le plus ancien est intéressé par la programmation fonctionnelle et lambdas et ainsi de suite, donc il n'est pas rebuté par elle.
même si je suis un fan des méthodes d'extension, Je ne pense pas que cela soit vraiment utile. Vous avez encore la répétition des expressions (dans la version monadique), et cela signifie Juste que vous devez expliquer Maybe
à tout le monde. La courbe d'apprentissage ajoutée ne semble pas avoir assez d'avantages dans ce cas.
la version IfNotNull
réussit au moins à éviter la répétition, mais je pense qu'elle est encore un peu trop longue sans être vraiment plus claire.
peut-être qu'un jour nous aurons un opérateur de déréférencement à sécurité nulle...
tout comme un côté, ma méthode d'extension semi-maléfique préférée est:
public static void ThrowIfNull<T>(this T value, string name) where T : class
{
if (value == null)
{
throw new ArgumentNullException(name);
}
}
qui vous permet de tourner ce:
void Foo(string x, string y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
...
}
en:
void Foo(string x, string y)
{
x.ThrowIfNull(nameof(x));
y.ThrowIfNull(nameof(y));
...
}
il y a encore la méchante répétition du nom du paramètre, mais au moins c'est plus ordonné. Bien sûr, dans .NET 4.0 j'utiliserais des contrats de Code, c'est ce sur quoi je suis censé écrire en ce moment... Stack Overflow est un grand travail d'évitement ;)
si vous voulez une méthode d'extension pour réduire le si imbriqué comme vous avez, vous pourriez essayer quelque chose comme ceci:
public static object GetProperty(this object o, Type t, string p)
{
if (o != null)
{
PropertyInfo pi = t.GetProperty(p);
if (pi != null)
{
return pi.GetValue(o, null);
}
return null;
}
return null;
}
alors dans votre code vous ne feriez que:
string activeControlName = (Form.ActiveForm as object)
.GetProperty(typeof(Form),"ActiveControl")
.GetProperty(typeof(Control),"Name");
Je ne sais pas si je voudrais l'utiliser souvent en raison de la lenteur de la réflexion, et je ne pense pas vraiment que ce beaucoup mieux que l'alternative, mais il devrait fonctionner, indépendamment de savoir si vous frappez un null en cours de route...
(Note: je pourrais avoir obtenu ces types mélangés) :)
dans le cas où vous avez affaire à C# 6.0 / VS 2015 et au-dessus, ils ont maintenant une solution intégrée pour la propagation nulle:
string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.
l'échantillon initial fonctionne et est le plus facile à lire en un coup d'oeil. Est-il vraiment nécessaire de l'améliorer?
la solution IfNotNull est la meilleure (jusqu'à ce que L'équipe C# nous donne un opérateur de déréférencement à sécurité nulle, c'est-à-dire).
Je ne suis pas fou des deux solutions. Quel était le problème avec la version asorter de l'original:
string activeControlName = null;
if (Form.ActiveForm != null)
if (Form.ActiveForm.ActivControl != null) activeControlname = activeControl.Name;
si ce n'est pas cela, alors je regarderais l'écriture D'un NotNullChain ou FluentNotNull objet qui peut enchaîner quelques tests pas null dans une rangée. Je suis d'accord que la méthode d'extension IfNotNull agissant sur un null semble un peu bizarre - même si les méthodes d'extension ne sont que du sucre syntaxique.
je pense que la réponse de Mark Synowiec pourrait faire générique.
à mon humble avis, je pense que le C# noyau de l'équipe devrait ressembler à ce "problème", même si je pense qu'il y a des choses plus importantes à résoudre.
sûr, original 2-niché si est beaucoup plus lisible que d'autres choix. Mais en suggérant que vous voulez résoudre le problème plus généralement, voici une autre solution:
try
{
var activeForm = Form.ActiveForm; assumeIsNotNull(activeForm);
var activeControl = activeForm.ActiveControl; assumeIsNotNull(activeControl);
var activeControlname = activeControl.Name;
}
catch (AssumptionChainFailed)
{
}
où
class AssumptionChainFailed : Exception { }
void assumeIsNotNull(object obj)
{
if (obj == null) throw new AssumptionChainFailed();
}