2010-02-18 6 views
4

Je veux vérifier si une simple expression mathématique déborderait (en utilisant checked et catch(OverflowException)), mais sans avoir besoin d'utiliser un bloc try-catch à chaque fois. Donc l'expression (pas le résultat!) Doit être passée à une fonction checkOverflow, qui agit alors en conséquence en cas de débordement.Portée lexicale dans C# lambda/délégués anonymes

C'est ce que j'ai essayé, mais cela ne fonctionne pas car il ne semble pas y avoir de portée lexicale pour les expressions lambda.

static void Main(string[] args) 
{ 
    double g, h; 

    // Check if the expression g+h would overflow, *without* the need to use 
    // try/catch around the expression 

    // Both of the following attempts fail because there's no lexical scoping 
    checkOverflow((FloatReturningExpression) delegate() {return g+h;}); 
    checkOverflow(() => g+h); 

    Console.ReadKey(); 
} 

private static void checkOverflow(FloatReturningExpression exp) 
{ 
    try 
    { 
     checked { double f = exp(); } 
    } 
    catch(OverflowException) 
    { 
     Console.WriteLine("overflow!"); 
    } 
} 

private delegate double FloatReturningExpression(); 

Y at-il une solution pour cela? (Travailler avec .NET 2, mais pas nécessairement.)

Répondre

7

Les nombres à virgule flottante dans .Net ne débordent pas de la même manière que les nombres arithmétiques entiers.

Ils vont obligeamment à Double.PositiveIfinity, ou Double.NegativeIfinity (spécifique dans les cas où l'opération mathématique devient Double.NaN invalide)

noter que ceci est également un peu plus complexe en raison du comportement à virgule flottante lorsque face à deux chiffres avec une précision très différente.

Console.WriteLine(double.MaxValue); 
Console.WriteLine(double.MaxValue * 2); 
Console.WriteLine(double.MaxValue + 1); 
Console.WriteLine(double.MaxValue + double.MaxValue); 

donne:

1.79769313486232E+308 
Infinity 
1.79769313486232E+308 
Infinity 

De plus on ne sait pas ce que vous voulez que votre fonction checkOverflow faire, il suffit d'écrire que cela est arrivé?

Si c'est tout cette approche fonctionnera (je converti en int pour vous)

void Main() 
{ 
    int a, b; 
    a = int.MaxValue; 
    b = 1; 

    // Check if the expression a+b would overflow, *without* the need to use 
    // try/catch around the expression 
    checkOverflow(() => {checked { return a+b; }});  
}  

private static void checkOverflow(Func<int> exp) 
{ 
    try 
    { 
     exp(); 
    } 
    catch(OverflowException) 
    { 
     Console.WriteLine("overflow!"); 
    } 
} 

Je dois ajouter la raison pour laquelle cela fonctionne:
vérifié n'est pas une portée lexicale dans le sens d'affecter les variables. C'est une région interprétée par le compilateur comme disant, tous code ici à l'intérieur qui fait l'arithmétique entière devrait générer les instructions de piégeage de débordement. Peu importe d'où viennent les variables, seulement quel code est défini où.

Je crois que votre modèle mental est quelque chose comme:

checked // enter a 'checked' state where all operations 
{  // (say on the current thread) are checked 

code, function calls, etc. etc 

} // leave the checked mode, all operations are now unchecked 

Ce n'est pas comment fonctionne vérifié, vérifié définit quelles instructions sont émises au moment de la compilation (un peu trop plein de piège instructions, certains ne le font pas)

Le bloc coché n'affecte PAS le code défini en dehors de celui-ci. Par exemple tout en utilisant les fonctions:

int Times2(int a) 
{ 
    return a * 2; 
} 

void TheresNoDifferenceHere() 
{ 
    checked { Times2(int.MaxValue); } 
    Times2(int.MaxValue); 
} 

L'appel de la fonction Times2 résout à quelque chose comme

IL_0000: nop   
IL_0001: ldarg.1  
IL_0002: ldc.i4.2  
IL_0003: mul   
IL_0004: stloc.0  
IL_0005: br.s  IL_0007 
IL_0007: ldloc.0  
IL_0008: ret 

Si vous aviez utilisé

int Times2(int a) 
{ 
    checked { return a * 2; } 
} 

IL_0000: nop   
IL_0001: nop   
IL_0002: ldarg.1  
IL_0003: ldc.i4.2  
IL_0004: mul.ovf  
IL_0005: stloc.0  
IL_0006: br.s  IL_0008 
IL_0008: ldloc.0  
IL_0009: ret   

noter la différence dans l'utilisation de Mul et mul.ovf . Ainsi, les deux appels ne peuvent pas être modifiés pour être vérifiés ou non après le fait. Le bloc coché autour du premier appel dans l'exemple ci-dessus a effectivement aucun effet sur l'IL résultant.Il n'y a pas d'opérations définies à l'intérieur qui lui importent. Ainsi, votre idée originale définissait l'opération arithmétique à un endroit (sans vérification) puis l'exécutait à un autre point (comme un appel de fonction) mais l'instruction 'vérifiée' ne peut pas affecter le code qu'elle n'a pas entouré au moment de la compilation. . Lambdas se résout à des arborescences d'expression ou à un délégué anonyme (peut-être soutenu par les classes synthétiques requises pour conserver et maintenir les variables liées à la fermeture). Dans les deux cas, l'aspect vérifié de n'importe quelle partie d'entre eux est entièrement défini là où ils sont définis, et non là où ils sont appelés.

+0

Pouvez-vous expliquer pourquoi le débordement n'est pas intercepté lors de l'exécution de 'checked {exp(); } 'dans le bloc try * au lieu de * dans le lambda? Quand est-ce que 'a + b' est évalué? – AndiDog

+0

@AndiDog, est-ce que le bit dans mon édition ne m'explique pas assez? Je vais essayer d'ajouter à cela. – ShuggyCoUk

+0

Bon, c'est logique maintenant. Merci – AndiDog