web-dev-qa-db-fra.com

Appel de méthode si non nul en C #

Est-il possible de raccourcir cette déclaration?

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

parce que j’écris souvent cela et que cela devient assez énervant. La seule chose à laquelle je peux penser est d'implémenter le motif 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 invoquer

if (MyEvent != null)
    MyEvent.Invoke();
81
Jakub Arnold

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

MyEvent?.Invoke();

ou:

obj?.SomeMethod();

Le ?. Est l'opérateur à propagation nulle et provoque le court-circuitage du .Invoke() lorsque l'opérande est null. L’opérande n’est accessible qu’une seule fois, il n’ya donc aucun risque que le problème "Les valeurs changent entre les vérifications et les invocations".

===

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

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

maintenant c'est valide:

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

Dans le cas d'événements, cela présente 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);
}

nous pouvons utiliser simplement:

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

Ce que vous recherchez, c'est l'opérateur Null Conditionnel (pas "coalescing"): ?.. Il est disponible à partir de C # 6.

Votre exemple serait obj?.SomeMethod();. Si obj est null, 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 obj est null, ce qui est important si l'évaluation des arguments aurait des effets secondaires.

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

26
Vimes

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"));
10
katbyte

Oui, en C 6.0 - https://msdn.Microsoft.com/en-us/magazine/dn802602.aspx .

object?.SomeMethod()
4
epylar

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

Aucune vérification de null n'est nécessaire.

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

Soyez conscient de l'impact potentiel sur les performances, cependant. Un micro-test rapide que j'ai indiqué indique que le traitement d'un événement sans abonné est deux à trois fois plus lent lorsque vous utilisez le modèle "délégué par défaut". (Sur mon ordinateur portable dual core 2,5 GHz, cela signifie 279 ms: 785 ms pour la création de 50 millions d’événements non abonnés.). Pour les points chauds d’application, cela pourrait être un problème à prendre en compte.

4
Sven Künzler

La méthode d’extension par cerating, comme on le suggère, ne résout pas vraiment les problèmes de conditions de concurrence, mais les cache.

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

Comme indiqué précédemment, ce code est l’équivalent élégant de la solution à variable temporaire, mais ...

Le problème avec les deux il est possible que le sous-type 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 survenir après que l'instance de délégué est copiée dans la variable temporaire (ou passée en tant que 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 un tel cas: l'état du composant ne pouvait pas déjà gérer la notification d'événement. Il est possible d’écrire du code client de manière à le gérer, mais cela imposerait une responsabilité inutile au client.

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

Pour être plus précis, le verrou doit être appliqué au même objet de synchronisation que celui utilisé dans les méthodes d’accesseur d’événement add\remove, qui est la valeur par défaut 'this'.

2
andrey.tsykunov

Je suis d'accord avec la réponse de Kenny Eliasson. Aller avec les méthodes d'extension. Voici un bref aperçu des méthodes d’extension et de la méthode IfNotNull requise.

Méthodes d'extension (méthode IfNotNull)

2
Rohit

This article de Ian Griffiths donne deux solutions différentes au problème qui, conclut-il, sont des astuces intéressantes que vous ne devriez pas utiliser.

2
Darrel Miller

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;
}
0
Kenny Eliasson