Appel de méthode s'il n'est pas nul en C #


106

Est-il possible de raccourcir d'une manière ou d'une autre cette déclaration?

if (obj != null)
    obj.SomeMethod();

parce que j'écris beaucoup ça et ça devient assez ennuyeux. La seule chose à laquelle je peux penser est d'implémenter le modèle Null Object , mais ce n'est pas ce que je peux faire à chaque fois et ce n'est certainement pas une solution pour raccourcir la syntaxe.

Et problème similaire avec les événements, où

public event Func<string> MyEvent;

puis invoquez

if (MyEvent != null)
    MyEvent.Invoke();

41
Nous avons envisagé d'ajouter un nouvel opérateur à C # 4: "obj.?SomeMethod ()" signifierait "appeler SomeMethod si obj n'est pas nul, sinon, retourner nul". Malheureusement, cela ne cadrait pas avec notre budget, nous ne l'avons donc jamais mis en œuvre.
Eric Lippert

@Eric: Ce commentaire est-il toujours valable? J'ai vu quelque part que, il est disponible avec 4.0?
CharithJ

@CharithJ: Non. Il n'a jamais été mis en œuvre.
Eric Lippert

3
@CharithJ: Je suis conscient de l'existence de l'opérateur de fusion nul. Il ne fait pas ce que Darth veut; il veut un opérateur d'accès aux membres levé en null. (Et au fait, votre commentaire précédent donne une caractérisation incorrecte de l'opérateur de fusion nul. Vous vouliez dire "v = m == null? Y: m. La valeur peut être écrite v = m ?? y".)
Eric Lippert

4
Pour les lecteurs plus récents: C # 6.0 implémente?., Donc x? .Y? .Z? .ToString () retournera null si x, y ou z sont nuls, ou retournera z.ToString () si aucun d'entre eux n'est nul.
David

Réponses:


162

À partir de C # 6, vous pouvez simplement utiliser:

MyEvent?.Invoke();

ou:

obj?.SomeMethod();

Le ?.est l'opérateur de propagation de null, et provoquera le .Invoke()court-circuit du lorsque l'opérande est null. L'opérande n'est accessible qu'une seule fois, il n'y a donc aucun risque de problème de "changement de valeur entre la vérification et l'appel".

===

Avant C # 6, non: il n'y a pas de magie sans valeur nulle, à une exception près; méthodes d'extension - par exemple:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

maintenant c'est valable:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

Dans le cas des événements, cela a l'avantage de supprimer également la condition de concurrence, c'est-à-dire que vous n'avez pas besoin d'une variable temporaire. Donc normalement vous auriez besoin de:

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

mais avec:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

on peut utiliser simplement:

SomeEvent.SafeInvoke(this); // no race condition, no null risk

1
'un peu en conflit à ce sujet. Dans le cas d'un gestionnaire d'action ou d'événement - qui est généralement plus indépendant - cela a du sens. Je ne casserais pas l'encapsulation pour une méthode régulière, cependant. Cela implique de créer la méthode dans une classe statique distincte et je ne pense pas que perdre l'encapsulation et dégrader la lisibilité / l'organisation du code dans son ensemble vaut l'amélioration mineure de la lisibilité locale
tvanfosson

@tvanfosson - en effet; mais mon point est que c'est le seul cas que je connaisse où cela fonctionnera. Et la question elle-même soulève le sujet des délégués / événements.
Marc Gravell

Ce code finit par générer une méthode anonyme quelque part, ce qui gâche vraiment la trace de pile de toute exception. Est-il possible de donner des noms de méthodes anonymes? ;)
sisve

Faux selon 2015 / C # 6.0 ...? est-il la solution. ReferenceTHatMayBeNull? .CallMethod () n'appellera pas la méthode lorsqu'elle est nulle.
TomTom

1
@mercu ça devrait être ?.- en VB14 et plus
Marc Gravell

27

Qu'est - ce que vous cherchez est la condition Null (non « coalescent ») opérateur: ?.. Il est disponible à partir de C # 6.

Votre exemple serait obj?.SomeMethod();. Si obj est nul, rien ne se passe. Lorsque la méthode a des arguments, par exemple, obj?.SomeMethod(new Foo(), GetBar());les arguments ne sont pas évalués si objest nul, ce qui est important si l'évaluation des arguments aurait des effets secondaires.

Et le chaînage est possible: myObject?.Items?[0]?.DoSomething()


1
C'est fantastique. Il est intéressant de noter qu'il s'agit d'une fonctionnalité C # 6 ... (ce qui serait implicite de votre déclaration VS2015, mais qui vaut toujours la peine d'être noté). :)
Kyle Goode

10

Une méthode d'extension rapide:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

exemple:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

Ou bien:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

exemple:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));

J'ai essayé de le faire avec des méthodes d'extension et je me suis retrouvé avec à peu près le même code. Cependant, le problème avec cette implémentation est qu'elle ne fonctionnera pas avec les expressions qui renvoient un type valeur. Il fallait donc avoir une deuxième méthode pour cela.
orad

4

Les événements peuvent être initialisés avec un délégué par défaut vide qui n'est jamais supprimé:

public event EventHandler MyEvent = delegate { };

Aucun contrôle nul nécessaire.

[ Mise à jour , merci à Bevan pour l'avoir signalé]

Soyez conscient de l'impact possible sur les performances, cependant. Un micro benchmark rapide que j'ai fait indique que la gestion d'un événement sans abonné est 2 à 3 fois plus lente lors de l'utilisation du modèle "délégué par défaut". (Sur mon ordinateur portable double cœur 2,5 GHz, cela signifie 279 ms: 785 ms pour augmenter 50 millions d'événements non abonnés.). Pour les points chauds d'application, cela peut être un problème à prendre en compte.


1
Ainsi, vous évitez une vérification nulle en invoquant un délégué vide ... un sacrifice mesurable de mémoire et de temps pour économiser quelques frappes? YMMV, mais pour moi, un mauvais compromis.
Bevan

J'ai également vu des benchmarks qui montrent qu'il est beaucoup plus coûteux d'invoquer un événement avec plusieurs délégués abonnés qu'avec un seul.
Greg D


2

Cet article d'Ian Griffiths donne deux solutions différentes au problème qui, selon lui, sont des astuces intéressantes que vous ne devriez pas utiliser.


3
Et en tant qu'auteur de cet article, j'ajouterais que vous ne devriez certainement pas l'utiliser maintenant que C # 6 résout ce problème avec les opérateurs conditionnels nuls (?. Et. []).
Ian Griffiths

2

La méthode d'extension de cérémonie comme celle suggérée ne résout pas vraiment les problèmes de conditions de course, mais les cache plutôt.

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

Comme indiqué, ce code est l'équivalent élégant de la solution avec une variable temporaire, mais ...

Le problème avec les deux est qu'il est possible que le sous-système de l'événement puisse être appelé APRÈS qu'il se soit désabonné de l'événement . Cela est possible car la désinscription peut se produire après que l'instance de délégué a été copiée dans la variable temporaire (ou passée en paramètre dans la méthode ci-dessus), mais avant que le délégué ne soit appelé.

En général, le comportement du code client est imprévisible dans ce cas: l'état du composant ne peut pas permettre déjà de gérer la notification d'événement. Il est possible d'écrire du code client de manière à le gérer, mais cela impliquerait une responsabilité inutile sur le client.

Le seul moyen connu de garantir la sécurité des threads consiste à utiliser l'instruction de verrouillage pour l'expéditeur de l'événement. Cela garantit que tous les abonnements \ désabonnements \ invocation sont sérialisés.

Pour être plus précis, le verrouillage doit être appliqué au même objet de synchronisation utilisé dans les méthodes d'accès aux événements add \ remove, qui est par défaut «this».


Ce n'est pas la condition de concurrence à laquelle il est fait référence. La condition de concurrence est if (MyEvent! = Null) // MyEvent n'est pas null maintenant MyEvent.Invoke (); // MyEvent est nul maintenant, de mauvaises choses se produisent Donc, avec cette condition de concurrence, je ne peux pas écrire un gestionnaire d'événements asynchrones dont le fonctionnement est garanti. Cependant, avec votre «condition de concurrence», je peux écrire un gestionnaire d'événements asynchrones qui est garanti pour fonctionner. Bien sûr, les appels vers l'abonné et le désabonné doivent être synchrones, ou un code de verrouillage y est requis.
jyoung

En effet. Mon analyse de ce problème est postée ici: blogs.msdn.com/ericlippert/archive/2009/04/29/…
Eric Lippert


1

Peut-être pas mieux mais à mon avis plus lisible est de créer une méthode d'extension

public static bool IsNull(this object obj) {
 return obj == null;
}

1
qu'est-ce que cela return obj == nullsignifie. What will it return
Hammad Khan

1
Cela signifie que si objest nullla méthode retourne true, je pense.
Joel

1
Comment cela répond-il même à la question?
Kugel

Réponse tardive, mais êtes-vous sûr que cela fonctionnerait même? Pouvez-vous appeler une méthode d'extension sur un objet qui l'est null? Je suis à peu près sûr que cela ne fonctionne pas avec les propres méthodes du type, donc je doute que cela fonctionne avec les méthodes d'extension non plus. Je crois que la meilleure façon de vérifier si un objet est null est obj is null. Malheureusement, pour vérifier si un objet ne l'est pas, il nullfaut être placé entre parenthèses, ce qui est regrettable.
natiiix

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.