2009-08-05 3 views
2

Supposons que vous utilisiez l'opérateur ternaire, ou l'opérateur de coalescence nulle, ou des instructions if-else imbriquées pour choisir l'affectation à un objet. Supposons maintenant que dans l'instruction conditionnelle, vous ayez l'évaluation d'une opération coûteuse ou volatile, nécessitant que vous mettiez le résultat dans une variable temporaire, en capturant son état, afin qu'il puisse être comparé, puis potentiellement affecté.How-to: court-circuiter l'opérateur ternaire inversé implémenté dans, par ex. C#? Est-ce que ça importe?

Comment un langage, tel que C#, à prendre en compte, implémenterait-il un nouvel opérateur logique pour gérer ce cas? Devrait-il? Existe-t-il des moyens de gérer ce cas en C#? Autres langues?

Certains cas de réduction de la verbosité d'un opérateur de coalescence ternaire ou nul ont été surmontés, lorsque nous supposons que nous recherchons des comparaisons directes, par exemple. Voir Unique ways to use the Null Coalescing operator, en particulier la discussion autour de la façon dont on peut étendre l'utilisation de l'opérateur pour soutenir String.IsNullOrEmpty(string). Notez comment Jon Skeet utilise le PartialComparer de MiscUtil, reformater 0 s à null s,

Pourquoi est-ce peut-elle nécessaire? Eh bien, jetez un oeil à la façon d'écrire une méthode de comparaison des objets complexes sans raccourcis (exemples des discussions citées):

public static int Compare(Person p1, Person p2) 
{ 
    return ((result = Compare(p1.Age, p2.Age)) != 0) ? result 
     : ((result = Compare(p1.Name, p2.Name)) != 0) ? result 
     : Compare(p1.Salary, p2.Salary); 
} 

Jon Skeet écrit une nouvelle comparaison de repli le cas d'égalité. Cela permet l'expression d'étendre en écrivant une nouvelle méthode spécifique qui renvoie la valeur null, ce qui nous permet d'utiliser l'opérateur coalescent null:

return PartialComparer.Compare(p1.Age, p2.Age) 
    ?? PartialComparer.Compare(p1.Name, p2.Name) 
    ?? PartialComparer.Compare(p1.Salary, p2.Salary) 
    ?? 0; 

L'opérateur coalescent null est plus lisible car il a deux côtés, pas trois. La clause de condition booléenne est séparée en une méthode, dans ce cas renvoyant null si l'expression doit être continuée. A quoi ressemblerait l'expression ci-dessus si nous pouvions mettre la condition en ligne plus facilement? Prenez l'expression de PartialComparer.Compare qui retourne null, et le placer dans une nouvelle expression ternaire qui nous permet d'utiliser l'évaluation de l'expression côté gauche, avec une variable temporaire implicite value:

return Compare(p1.Age, p2.Age) unless value == 0 
    : Compare(p1.Name, p2.Name) unless value == 0 
    : Compare(p1.Salary, p2.Salary); 

Le « flux » de base d'une expression serait:

expressionA moins quebooleanB dans lequel ca SE expressionC

Plutôt que d'être un opérateur de comparaison surchargé, je suppose que cela est plus comme un court-circuit opérateur ternaire inversé.

  • Ce type de logique serait-il utile? Actuellement, la fusion nulle nous fournit un moyen de le faire avec l'expression conditionnelle (value == null).
  • Quelles autres expressions souhaitez-vous tester? Nous avons entendu parler de (String.IsNullOrEmpty(value)).
  • Quel serait le meilleur moyen d'exprimer cela dans la langue, en termes d'opérateurs, de mots-clés?
+0

"Pas une vraie question", hein? Je suppose que "Qu'est-ce que vous utilisez pour garder des notes en tant que développeur" @ http://stackoverflow.com/questions/78756/ et "Y at-il des programmeurs one-man-army célèbres @ http://stackoverflow.com/questions/ 529757 /, avec d'autres allant de "Quelle est votre bande dessinée programmeur préférée" à "De bonnes citations de programmation" sont tellement plus réel que de discuter de la conception du langage de programmation.Je suppose que je devrais retag l'une des balises ci-dessus à «subjective», mais comment – maxwellb

+0

, le cas d'utilisation représenté ici ne permet pas d'utiliser facilement 'if (B) C else A', car B dépend de l'évaluation de C ou A. – maxwellb

+0

@mpbloch: cliquez sur éditer pour changer les mots-clés sur votre question et btw, c'est une question intéressante, pas de raison de la fermer IMHO ... –

Répondre

4

Personnellement, j'éviter le court-circuit des opérateurs et laisser les méthodes enchaîner:

public static int CompareChain<T>(this int previous, T a, T b) 
{ 
    if (previous != 0) 
     return previous; 
    return Comparer<T>.Default.Compare(a,b); 
} 

utilisation comme ceci:

int a = 0, b = 2; 
string x = "foo", y = "bar"; 
return a.Compare(b).CompareChain(x,y); 

peut être inline par le JIT afin qu'il puisse effectuer tout comme le court-circuitage intégré dans la langue sans pour autant compliquer la tâche. En réponse à votre question de savoir si la 'structure' ci-dessus peut s'appliquer à plus que des comparaisons, alors oui, elle peut, en choisissant de continuer ou non et d'être expliquée et contrôlable par l'utilisateur. Ceci est intrinsèquement plus complexe mais, l'opération est plus flexible donc c'est inévitable.

public static T ElseIf<T>(
    this T previous, 
    Func<T,bool> isOK 
    Func<T> candidate) 
{ 
    if (previous != null && isOK(previous)) 
     return previous; 
    return candidate(); 
} 

utilisent alors comme si

Connection bestConnection = server1.GetConnection() 
    .ElseIf(IsOk, server2.GetConnection) 
    .ElseIf(IsOk, server3.GetConnection) 
    .ElseIf(IsOk,() => null); 

Ceci est une flexibilité maximale dans ce que vous pouvez modifier le IsOk vérifier à tout moment et que vous êtes tout à fait paresseux. Pour les situations où le contrôle OK est le même dans tous les cas, vous pouvez simplifier comme cela et éviter complètement les méthodes d'extensions.

public static T ElseIf<T>(  
    Func<T,bool> isOK 
    IEnumerable<Func<T>[] candidates) 
{ 
    foreach (var candidate in candidates) 
    { 
     var t = candidate(); 
     if (isOK(t)) 
      return t; 
    } 
    throw new ArgumentException("none were acceptable"); 
} 

Vous pouvez le faire avec LINQ, mais cette façon donne un beau message d'erreur et permet cette

public static T ElseIf<T>(  
    Func<T,bool> isOK 
    params Func<T>[] candidates) 
{ 
    return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates); 
} 
le style

qui conduit à bien un code lisible comme ceci:

var bestConnection = ElseIf(IsOk, 
    server1.GetConnection, 
    server2.GetConnection, 
    server3.GetConnection); 

Si vous voulez autoriser une valeur par défaut alors:

public static T ElseIfOrDefault<T>(  
    Func<T,bool> isOK 
    IEnumerable<Func<T>>[] candidates) 
{ 
    foreach (var candidate in candidates) 
    { 
     var t = candidate(); 
     if (isOK(t)) 
      return t; 
    } 
    return default(T); 
} 

Il est évident que tout ce qui précède peut très facilement être écrit en utilisant lambdas de sorte que votre exemple spécifique serait:

var bestConnection = ElseIfOrDefault(
    c => c != null && !(c.IsBusy || c.IsFull), 
    server1.GetConnection, 
    server2.GetConnection, 
    server3.GetConnection); 
+0

J'apprécierais que vous m'aidiez à vérifier mon utilisation du C# 3 ci-dessus J'aime bien où vous preniez ceci, mais je voulais sur plus de cas que "Comparaison". – maxwellb

+0

Wow. Merci. Je savais que C# était un langage assez expressif, et qu'il y avait certainement des façons de le faire avec une poignée de méthodes, et posait donc la question originale pour voir si cela ajouterait quelque chose en exprimant cette logique avec un tel opérateur. Mais, vous avez certainement démontré l'expressivité offerte par la langue existante très complètement. L'exemple que vous avez fourni semble complet et traite de tous les cas où «l'opération logique» proposée s'appliquerait. La méthode générique ElseIf et les méthodes ElseIfOrDefault que vous fournissez semblent extrêmement flexibles. – maxwellb

+0

Si vous aimez cela, alors je suggère de jeter un oeil aux langages fonctionnels en général (f # étant un moyen agréable de le faire si vous êtes habitué à .Net) – ShuggyCoUk

0

Pour placer une implémentation proposée loin d'une question très verbeuse, exécutons le mot clé unless.

(expression A) unless (booléen B) < magique "auquel cas" opérateur> (expression C)

... serait tout ce qu'il ya à faire.

expression booléenne B aurait accès à l'évaluation de l'expression A par le mot-clé value. Expression C peut avoir le mot-clé unless dans son expression, permettant un enchaînement linéaire simple.

Les candidats au < magique "auquel cas" opérateur>:

  • :
  • |
  • ?:
  • otherwise mot-clé
0

L'utilisation de tous les symboles ont tendance à diminuer lisibilité pour l'av développeur d'erage. Même l'opérateur ?? n'est pas utilisé largement. Moi-même, je préfère développer du code verbeux, mais je peux facilement le lire dans un an.

Ainsi, un candidat pour votre:

expression A moins booléenne B auquel cas expression C.

serait

expression A moins booléen B sothen expression C.

Bien que beaucoup de gens comme moi utiliseraient encore:

if (B) {expression C;} 
else {expression A;} 

Cela vient lorsque vous développez un logiciel avec une grande équipe, d'horizons différents, chacun sur la maître d'équipe d'une langue, et juste utilisateur des autres.

+0

Cependant, je fais cela? – maxwellb

0

Plus @ShuggyCoUk: Ah, je vois que cela pourrait fonctionner pour plus que des comparaisons? Je ne l'ai pas utilisé C# 3 et les méthodes d'extension, mais je suppose que vous pouvez déclarer, pour mon exemple précédent, ci-dessous, un

public delegate bool Validation<T>(T toTest); 
public static T Validate<T>(this T leftside, Validation<T> validator) 
{ 
    return validator(leftside) ? leftside : null; 
} 

Suivi par, par Skeet:

Validation<Connection> v = (Connection c) => (c != null && !(c.IsBusy || c. IsFull)); 
Connection bestConnection = 
    server1.GetConnection().Validate(v) ?? 
    server2.GetConnection().Validate(v) ?? 
    server3.GetConnection().Validate(v) ?? null; 

Est-ce comment cela travaillerait en C#? Commentaires appréciés. Je vous remercie.


En réponse à ShuggyCoUk:

C'est donc une méthode d'extension en C# 3, alors? En outre, le résultat ici est un int, pas une expression arbitraire. Utile pour surcharger encore une autre méthode de comparaison. Supposons que je veuille une expression pour choisir la meilleure connexion.Idéalement, je veux quelque chose de simplifier les éléments suivants:

Connection temp; 
Connection bestConnection = 
    (temp = server1.GetConnection()) != null && !(temp.IsBusy || temp.IsFull) ? temp 
    : (temp = server2.GetConnection()) != null && !(temp.IsBusy || temp.IsFull) ? temp 
     : (temp = server3.GetConnection()) != null && !(temp.IsBusy || temp.IsFull) ? temp 
      : null; 

Ok, donc on pourrait avoir une méthode

bool IsOk(Connection c) 
{ 
    return (c != null && !(c.IsBusy || c.IsFull)); 
} 

qui produirait:

Connection temp; 
Connection bestConnection = 
    (temp = server1.GetConnection()) && IsOk(temp) ? temp 
    : (temp = server2.GetConnection()) && IsOk(temp) ? temp 
     : (temp = server3.GetConnection()) && IsOk(temp) ? temp 
      : null; 

Mais comment méthode enchaînant des comparaisons travaille ici? Je méditais quelque chose qui ressemble à:

Connection bestConnection = 
    server1.GetConnection() unless !IsOk(value) otherwise 
    server2.GetConnection() unless !IsOk(value) otherwise 
    server3.GetConnection() unless !IsOk(value) otherwise null; 

Je pense qu'il ya jusqu'à présent, cerceaux pour sauter à travers, si je veux le résultat d'une condition d'être une expression ou le résultat d'une méthode qui était dans l'original conditionnel. Je suppose que l'objet retourné par de telles méthodes sera cher à produire, ou changera la prochaine fois que la méthode est appelée.

+0

a expliqué comment utiliser mon approche fonctionnelle avec l'exemple révisé en édition à mon original répondre – ShuggyCoUk

Questions connexes