Quel est le ?[]? syntaxe en C #?

85

Pendant que j'étudiais le délégué qui est en fait une classe abstraite Delegate.cs, j'ai vu la méthode suivante dans laquelle je ne comprends pas

  • Pourquoi la valeur de retour utilise-t- ?elle bien qu'elle soit déjà un type de référence ( classe )
  • ?[]? signification sur le paramètre

Pourriez-vous expliquer?

public static Delegate? Combine(params Delegate?[]? delegates)
{
    if (delegates == null || delegates.Length == 0)
        return null;

    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
snr - Réintégrer Monica
la source
23
N'est-ce pas un tableau nullable qui peut contenir des valeurs nullables?
Cid
3
c # 8, vous pouvez maintenant spécifier qu'une variable objet n'est pas autorisée à être nulle. Si vous retournez le drapeau du compilateur, vous devez spécifier toutes les variables qui est autorisé à être nulle.
Jeremy Lakeman

Réponses:

66

Explication étape par étape:

params Delegate?[] delegates - C'est un tableau de nullable Delegate

params Delegate?[]? delegates - L'ensemble du tableau peut être nullable

Étant donné que chaque paramètre est du type Delegate?et que vous retournez un index du Delegate?[]?tableau, il est logique que le type de retour soit Delegate?sinon le compilateur retournerait une erreur comme si vous returiez et à intpartir d'une méthode qui renvoie une chaîne.

Vous pouvez par exemple changer votre code pour retourner un Delegatetype comme celui-ci:

public static Delegate Combine(params Delegate?[]? delegates)
{
    Delegate defaulDelegate = // someDelegate here
    if (delegates == null || delegates.Length == 0)
        return defaulDelegate;

    Delegate d = delegates[0] ?? defaulDelegate;
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
Athanasios Kataras
la source
4
qu'en est-il de ma première question? Pourquoi la valeur de retour utilise-t- ?elle bien qu'elle soit déjà un type de référence (classe)
snr - Rétablir Monica
2
Parce que vous revenez d qui est Délégué? type. Et également null ou Délégué en fonction des paramètres
Athanasios Kataras
2
@snr La valeur de retour utilise le ?pour exactement la même raison pour laquelle la valeur d'argument utilise le ?. Si vous comprenez ce dernier, vous comprenez automatiquement le premier. Parce que la méthode peut retourner null , comme le paramètre peut accepter null . Autrement dit, ils peuvent tous deux contenir null .
GSerg
2
Je l'ai touché. Je viens de répondre en tant que deuxième partie avec l'exemple. Since each parameter is of the type Delegate? and you return an index of the Delegate?[]? array, then it makes sense that the return type is Delegate?
Athanasios Kataras
2
@snr: Pourquoi la valeur de retour utilise-t-elle? bien que ce soit déjà un type de référence (classe) (1) Cela peut être une habitude ou un reste de refactorisation si des structures sont également utilisées dans un code similaire (2) En C # 8, les types de référence peuvent être interdits pour être intrinsèquement annulables (nécessitant ainsi une annulation explicite ( 3) Foo?a une HasValuepropriété soignée , alors Fooqu'il doit être == nullvérifié (4) Il communique aux développeurs qui lisent la signature de la méthode que les valeurs nulles sont un résultat possible, attendu et correct. (5) Le code semble être écrit par quelqu'un qui aime beaucoup la nullité et est explicite à ce sujet.
Flater
25

Les types de référence nullables sont nouveaux dans C # 8.0, ils n'existaient pas auparavant.

C'est une question de documentation et de la façon dont les avertissements sont générés au moment de la compilation.

L'exception «objet non défini sur une instance d'un objet» est une exception courante. Mais il s'agit d'une exception d'exécution, elle peut déjà être partiellement découverte au moment de la compilation.

Pour un réglement, Delegate dvous pouvez toujours appeler

 d.Invoke();

ce qui signifie, vous pouvez le coder, au moment de la compilation, rien ne se passera. Il peut soulever des exceptions lors de l'exécution.

Alors que pour un nouveau Delegate? pcode

 p.Invoke();

produira un avertissement du compilateur. CS8602: Dereference of a possibly null reference sauf si vous écrivez:

 p?.Invoke();

ce qui signifie, appelez uniquement s'il n'est pas nul.

Donc, vous documentez une variable peut contenir null ou non. Il déclenche des avertissements plus tôt et peut éviter plusieurs tests pour null. La même chose que vous avez pour int et int?. Vous savez avec certitude, l'un n'est pas nul - et vous savez comment convertir l'un en l'autre.

Holger
la source
avec la mise en garde que vous ne savez pas avec certitude qui Delegaten'est pas nulle. Vous prétendez simplement que vous savez avec certitude (ce qui est suffisant dans la plupart des cas).
Tim Pohlmann
@TimPohlmann Ainsi qu'int i = null est impossible, une chaîne s = null est impossible (en C # 8 avec ce changement de rupture). C'est donc un peu plus que faire semblant. Pour des raisons de compatibilité descendante, il est déclassé en avertissement. Si vous mettez à niveau l'avertissement en une erreur, vous savez à coup sûr qu'il n'est pas nul.
Holger
4
La dernière fois que j'ai vérifié, les TRN ne sont pas à 100% à l'épreuve des balles. Il existe certains cas marginaux que le compilateur ne peut pas détecter.
Tim Pohlmann
Oui, mais c'est la même chose que pour les avertissements "variable non initialisée" ou "pas tous les chemins de code renvoyant une valeur". Ce sont toutes des fonctionnalités de compilation à 100%, sans détection lors de l'exécution. Contrairement à int ?, je pense qu'ils n'ajoutent pas de mémoire supplémentaire pour contenir des informations supplémentaires. Disons que c'est aussi fiable qu'intellisense. Assez bien.
Holger
1
Si vous en avez un, intil ne peut jamais être nul. Si vous en avez un, Delegateil peut être nul (pour diverses raisons, par exemple la réflexion). Il est généralement prudent de supposer que Delegate(en C # 8 avec NRT activé) n'est pas nul, mais vous ne savez jamais avec certitude (où pour intnous savons avec certitude).
Tim Pohlmann
5

En C # 8, il faut explicitement marquer les types de référence comme nullables.

Par défaut, ces types ne peuvent pas contenir null, un peu comme les types de valeur. Bien que cela ne change pas la façon dont les choses fonctionnent sous le capot, le vérificateur de type vous demandera de le faire manuellement.

Le code donné est refactorisé pour fonctionner avec C # 8, mais il ne bénéficie pas de cette nouvelle fonctionnalité.

public static Delegate? Combine(params Delegate?[]? delegates)
{
    // ...[]? delegates - is not null-safe, so check for null and emptiness
    if (delegates == null || delegates.Length == 0)
        return null;

    // Delegate? d - is not null-safe too
    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}

Voici un exemple de code mis à jour (ne fonctionnant pas, juste une idée) exploitant cette fonctionnalité. Cela nous a sauvé d'un contrôle nul et a un peu simplifié cette méthode.

public static Delegate? Combine(params Delegate[] delegates)
{
    // `...[] delegates` - is null-safe, so just check if array is empty
    if (delegates.Length == 0) return null;

    // `d` - is null-safe too, since we know for sure `delegates` is both not null and not empty
    Delegate d = delegates[0];

    for (int i = 1; i < delegates.Length; i++)
        // then here is a problem if `Combine` returns nullable
        // probably, we can add some null-checks here OR mark `d` as nullable
        d = Combine(d, delegates[i]);

    return d;
}
amankkg
la source
2
those types are not able to contain null, notez qu'il génère un avertissement du compilateur , pas une erreur. Le code fonctionnera correctement (bien qu'il puisse générer des exceptions NullReferenceExceptions). Cela se fait dans un souci de compatibilité descendante.
JAD