Comment puis-je faire cela élégamment avec C # et .NET 3.5/4?
Par exemple, un nombre peut être compris entre 1 et 100.
Je connais un simple si suffirait; mais le mot clé de cette question est l'élégance. C'est pour mon projet de jouet pas pour la production.
Cette question ne portait pas sur la vitesse, mais sur la beauté du code. Arrêtez de parler d'efficacité et autres; souviens-toi que tu prêches à la chorale.
Il y a beaucoup d'options:
int x = 30;
if (Enumerable.Range(1,100).Contains(x))
//true
if (x >= 1 && x <= 100)
//true
Consultez également ce SO post pour les options regex.
Tu veux dire?
if(number >= 1 && number <= 100)
ou
bool TestRange (int numberToCheck, int bottom, int top)
{
return (numberToCheck >= bottom && numberToCheck <= top);
}
Juste pour ajouter au bruit ici, vous pouvez créer une méthode d'extension:
public static bool IsWithin(this int value, int minimum, int maximum)
{
return value >= minimum && value <= maximum;
}
Ce qui vous permettrait de faire quelque chose comme ...
int val = 15;
bool foo = val.IsWithin(5,20);
Cela étant dit, cela semble être une chose stupide à faire lorsque le chèque lui-même n’est qu’une ligne.
Comme d'autres l'ont dit, utilisez un simple if.
Vous devriez penser à la commande.
par exemple
1 <= x && x <= 100
est plus facile à lire que
x >= 1 && x <= 100
Vous pouvez réduire le nombre de comparaisons de deux à un en utilisant des calculs. L'idée est que l'un des deux facteurs devient négatif si le nombre est en dehors de la plage et zéro si le nombre est égal à l'une des bornes:
Si les limites sont inclusives:
(x - 1) * (100 - x) >= 0
ou
(x - min) * (max - x) >= 0
Si les limites sont exclusives:
(x - 1) * (100 - x) > 0
ou
(x - min) * (max - x) > 0
Cependant, dans le code de production, j’écrirais simplement 1 < x && x < 100
, c’est plus facile à comprendre.
Avec un peu d'abus de méthode d'extension, nous pouvons obtenir la solution "élégante" suivante:
using System;
namespace Elegant {
public class Range {
public int Lower { get; set; }
public int Upper { get; set; }
}
public static class Ext {
public static Range To(this int lower, int upper) {
return new Range { Lower = lower, Upper = upper };
}
public static bool In(this int n, Range r) {
return n >= r.Lower && n <= r.Upper;
}
}
class Program {
static void Main() {
int x = 55;
if (x.In(1.To(100)))
Console.WriteLine("it's in range! elegantly!");
}
}
}
Je propose ceci:
public static bool IsWithin<T>(this T value, T minimum, T maximum) where T : IComparable<T> {
if (value.CompareTo(minimum) < 0)
return false;
if (value.CompareTo(maximum) > 0)
return false;
return true;
}
Exemples:
45.IsWithin(32, 89)
true
87.2.IsWithin(87.1, 87.15)
false
87.2.IsWithin(87.1, 87.25)
true
et bien sûr avec des variables:
myvalue.IsWithin(min, max)
Il est facile à lire (proche du langage humain) et fonctionne avec n'importe quel type comparable (entier, double, type personnalisé ...).
Avoir un code facile à lire est important car le développeur ne gaspillera pas les "cycles du cerveau" pour le comprendre. Au cours de longues sessions de codage, des cycles cérébraux inutiles rendent le développeur fatigué plus tôt et sujet aux bogues.
Si cela est accessoire, une simple if
est tout ce dont vous avez besoin. Si cela se produit dans de nombreux endroits, vous pouvez envisager les deux suivants:
Quelque chose comme:
[Between("parameter", 0, 100)]
public void Foo(int parameter)
{
}
Utiliser une expression &&
pour joindre deux comparaisons est simplement la manière la plus élégante de le faire. Si vous essayez d'utiliser des méthodes d'extension sophistiquées et autres, vous vous demandez si vous souhaitez inclure la limite supérieure, la limite inférieure ou les deux. Une fois que vous commencez à ajouter des variables supplémentaires ou à modifier les noms d'extension pour indiquer ce qui est inclus, votre code devient plus long et plus difficile à lire (pour la grande majorité des programmeurs). En outre, des outils tels que Resharper vous avertiront si votre comparaison n'a pas de sens (number > 100 && number < 1
), ce qu'ils ne feront pas si vous utilisez une méthode ('i.IsBetween (100, 1)').
Le seul autre commentaire que je ferais est que si vous vérifiez les entrées avec l'intention de lever une exception, vous devriez envisager d'utiliser des contrats de code:
Contract.Requires(number > 1 && number < 100)
Cela est plus élégant que if(...) throw new Exception(...)
et vous pouvez même recevoir des avertissements lors de la compilation si quelqu'un essaie d'appeler votre méthode sans s'assurer que le nombre est dans les limites en premier.
if (value > 1 && value < 100)
{
// do work
}
else
{
// handle outside of range logic
}
Parce que toutes les autres réponses ne sont pas inventées par moi, voici juste mon implémentation:
public enum Range
{
/// <summary>
/// A range that contains all values greater than start and less than end.
/// </summary>
Open,
/// <summary>
/// A range that contains all values greater than or equal to start and less than or equal to end.
/// </summary>
Closed,
/// <summary>
/// A range that contains all values greater than or equal to start and less than end.
/// </summary>
OpenClosed,
/// <summary>
/// A range that contains all values greater than start and less than or equal to end.
/// </summary>
ClosedOpen
}
public static class RangeExtensions
{
/// <summary>
/// Checks if a value is within a range that contains all values greater than start and less than or equal to end.
/// </summary>
/// <param name="value">The value that should be checked.</param>
/// <param name="start">The first value of the range to be checked.</param>
/// <param name="end">The last value of the range to be checked.</param>
/// <returns><c>True</c> if the value is greater than start and less than or equal to end, otherwise <c>false</c>.</returns>
public static bool IsWithin<T>(this T value, T start, T end) where T : IComparable<T>
{
return IsWithin(value, start, end, Range.ClosedOpen);
}
/// <summary>
/// Checks if a value is within the given range.
/// </summary>
/// <param name="value">The value that should be checked.</param>
/// <param name="start">The first value of the range to be checked.</param>
/// <param name="end">The last value of the range to be checked.</param>
/// <param name="range">The kind of range that should be checked. Depending on the given kind of range the start end end value are either inclusive or exclusive.</param>
/// <returns><c>True</c> if the value is within the given range, otherwise <c>false</c>.</returns>
public static bool IsWithin<T>(this T value, T start, T end, Range range) where T : IComparable<T>
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (start == null)
throw new ArgumentNullException(nameof(start));
if (end == null)
throw new ArgumentNullException(nameof(end));
switch (range)
{
case Range.Open:
return value.CompareTo(start) > 0
&& value.CompareTo(end) < 0;
case Range.Closed:
return value.CompareTo(start) >= 0
&& value.CompareTo(end) <= 0;
case Range.OpenClosed:
return value.CompareTo(start) > 0
&& value.CompareTo(end) <= 0;
case Range.ClosedOpen:
return value.CompareTo(start) >= 0
&& value.CompareTo(end) < 0;
default:
throw new ArgumentException($"Unknown parameter value {range}.", nameof(range));
}
}
}
Vous pouvez ensuite l'utiliser comme ceci:
var value = 5;
var start = 1;
var end = 10;
var result = value.IsWithin(start, end, Range.Closed);
Si vous voulez écrire plus de code qu'un simple if, vous pouvez peut-être: Créer une méthode d'extension appelée IsBetween
public static class NumberExtensionMethods
{
public static bool IsBetween(this long value, long Min, long Max)
{
// return (value >= Min && value <= Max);
if (value >= Min && value <= Max) return true;
else return false;
}
}
...
// Checks if this number is between 1 and 100.
long MyNumber = 99;
MessageBox.Show(MyNumber.IsBetween(1, 100).ToString());
Addendum: il est intéressant de noter qu'en pratique, il est très rare que vous "vérifiiez simplement l'égalité" (ou <,>) dans une base de code. (À part dans les situations les plus triviales.) À titre d’exemple, tout programmeur de jeu utiliserait des catégories similaires à celles décrites ci-après dans chaque projet. Notez que dans cet exemple, il se trouve qu’il utilise une fonction (Mathf.Approximately) intégrée à cet environnement; dans la pratique, vous devez généralement développer avec soin vos propres concepts de comparaison entre les représentations informatiques de nombres réels et le type de situation que vous êtes en train de concevoir. (Ne dites même pas que si vous faites quelque chose comme, par exemple, un contrôleur, un contrôleur PID ou autre, tout le problème devient central et très difficile, cela devient la nature du projet.) Le PO question ici une question triviale ou sans importance.
private bool FloatLessThan(float a, float b)
{
if ( Mathf.Approximately(a,b) ) return false;
if (a<b) return true;
return false;
}
private bool FloatLessThanZero(float a)
{
if ( Mathf.Approximately(a,0f) ) return false;
if (a<0f) return true;
return false;
}
private bool FloatLessThanOrEqualToZero(float a)
{
if ( Mathf.Approximately(a,0f) ) return true;
if (a<0f) return true;
return false;
}
Que diriez-vous quelque chose comme ça?
if (theNumber.isBetween(low, high, IntEx.Bounds.INCLUSIVE_INCLUSIVE))
{
}
avec la méthode d'extension suivante (testée):
public static class IntEx
{
public enum Bounds
{
INCLUSIVE_INCLUSIVE,
INCLUSIVE_EXCLUSIVE,
EXCLUSIVE_INCLUSIVE,
EXCLUSIVE_EXCLUSIVE
}
public static bool isBetween(this int theNumber, int low, int high, Bounds boundDef)
{
bool result;
switch (boundDef)
{
case Bounds.INCLUSIVE_INCLUSIVE:
result = ((low <= theNumber) && (theNumber <= high));
break;
case Bounds.INCLUSIVE_EXCLUSIVE:
result = ((low <= theNumber) && (theNumber < high));
break;
case Bounds.EXCLUSIVE_INCLUSIVE:
result = ((low < theNumber) && (theNumber <= high));
break;
case Bounds.EXCLUSIVE_EXCLUSIVE:
result = ((low < theNumber) && (theNumber < high));
break;
default:
throw new System.ArgumentException("Invalid boundary definition argument");
}
return result;
}
}
Je ferais un objet Range, quelque chose comme ceci:
public class Range<T> where T : IComparable
{
public T InferiorBoundary{get;private set;}
public T SuperiorBoundary{get;private set;}
public Range(T inferiorBoundary, T superiorBoundary)
{
InferiorBoundary = inferiorBoundary;
SuperiorBoundary = superiorBoundary;
}
public bool IsWithinBoundaries(T value){
return InferiorBoundary.CompareTo(value) > 0 && SuperiorBoundary.CompareTo(value) < 0;
}
}
Ensuite, vous l'utilisez de cette façon:
Range<int> myRange = new Range<int>(1,999);
bool isWithinRange = myRange.IsWithinBoundaries(3);
De cette façon, vous pouvez le réutiliser pour un autre type.
static class ExtensionMethods
{
internal static bool IsBetween(this double number,double bound1, double bound2)
{
return Math.Min(bound1, bound2) <= number && number <= Math.Max(bound2, bound1);
}
internal static bool IsBetween(this int number, double bound1, double bound2)
{
return Math.Min(bound1, bound2) <= number && number <= Math.Max(bound2, bound1);
}
}
Utilisation
numéro doubleToBeChecked = 7;
var result = numberToBeChecked.IsBetween (100, 122);
var résultat = 5.IsBetween (100,120);
résultat var = 8,0.IsBetween (1.2,9.6);
En C, si l’efficacité en termes de temps est cruciale et que des débordements d’entiers seront bouclés, on pourrait faire if ((unsigned)(value-min) <= (max-min)) ...
. Si 'max' et 'min' sont des variables indépendantes, la soustraction supplémentaire pour (max-min) perdra du temps, mais si cette expression peut être précalculée au moment de la compilation, ou si elle peut être calculée une fois au moment de l'exécution pour en tester plusieurs chiffres par rapport à la même plage, l'expression ci-dessus peut être calculée efficacement même dans le cas où la valeur est dans la plage (si une grande fraction de valeurs se situe en dessous de la plage valide, il sera peut-être plus rapide d'utiliser if ((value >= min) && (value <= max)) ...
car il quittera early si la valeur est inférieure à min).
Avant d’utiliser une telle implémentation, testez une machine cible. Sur certains processeurs, l'expression en deux parties peut être plus rapide dans tous les cas, car les deux comparaisons peuvent être effectuées indépendamment alors que dans la méthode soustraction et comparaison, la soustraction doit être terminée avant que la comparaison ne puisse être exécutée.
Élégant, car il ne vous oblige pas à déterminer laquelle des deux valeurs limites est supérieure en premier Il ne contient pas non plus de branches.
public static bool InRange(float val, float a, float b)
{
// Determine if val lies between a and b without first asking which is larger (a or b)
return ( a <= val & val < b ) | ( b <= val & val < a );
}
Une nouvelle tournure sur un vieux favori:
public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) {
if (includeBoundaries)
return number <= topOfRange && number >= bottomOfRange;
return number < topOfRange && number > bottomOfRange;
}
J'irais avec la version plus simple:
if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }
Je ne sais pas mais j'utilise cette méthode:
public static Boolean isInRange(this Decimal dec, Decimal min, Decimal max, bool includesMin = true, bool includesMax = true ) {
return (includesMin ? (dec >= min) : (dec > min)) && (includesMax ? (dec <= max) : (dec < max));
}
Et voici comment je peux l'utiliser:
[TestMethod]
public void IsIntoTheRange()
{
decimal dec = 54;
Boolean result = false;
result = dec.isInRange(50, 60); //result = True
Assert.IsTrue(result);
result = dec.isInRange(55, 60); //result = False
Assert.IsFalse(result);
result = dec.isInRange(54, 60); //result = True
Assert.IsTrue(result);
result = dec.isInRange(54, 60, false); //result = False
Assert.IsFalse(result);
result = dec.isInRange(32, 54, false, false);//result = False
Assert.IsFalse(result);
result = dec.isInRange(32, 54, false);//result = True
Assert.IsTrue(result);
}
Voici quelques méthodes d’extension qui peuvent aider
public static bool IsInRange<T>(this T value, T min, T max)
where T : System.IComparable<T>
{
return value.IsGreaterThenOrEqualTo(min) && value.IsLessThenOrEqualTo(max);
}
public static bool IsLessThenOrEqualTo<T>(this T value, T other)
where T : System.IComparable<T>
{
var result = value.CompareTo(other);
return result == -1 || result == 0;
}
public static bool IsGreaterThenOrEqualTo<T>(this T value, T other)
where T : System.IComparable<T>
{
var result = value.CompareTo(other);
return result == 1 || result == 0;
}
Lorsque vous vérifiez si un "nombre" se situe dans une plage, vous devez bien comprendre ce que vous voulez dire, et qu'est-ce que deux nombres sont égaux? En général, vous devriez envelopper tous les nombres à virgule flottante dans ce que l’on appelle une «boule epsilon». Pour ce faire, choisissez une petite valeur et indiquez que si deux valeurs sont aussi proches, elles sont identiques.
private double _epsilon = 10E-9;
/// <summary>
/// Checks if the distance between two doubles is within an epsilon.
/// In general this should be used for determining equality between doubles.
/// </summary>
/// <param name="x0">The orgin of intrest</param>
/// <param name="x"> The point of intrest</param>
/// <param name="epsilon">The minimum distance between the points</param>
/// <returns>Returns true iff x in (x0-epsilon, x0+epsilon)</returns>
public static bool IsInNeghborhood(double x0, double x, double epsilon) => Abs(x0 - x) < epsilon;
public static bool AreEqual(double v0, double v1) => IsInNeghborhood(v0, v1, _epsilon);
Avec ces deux aides en place et en supposant que si n'importe quel nombre peut être jeté comme un double sans la précision requise. Tout ce dont vous aurez besoin maintenant est une énumération et une autre méthode
public enum BoundType
{
Open,
Closed,
OpenClosed,
ClosedOpen
}
L'autre méthode suit:
public static bool InRange(double value, double upperBound, double lowerBound, BoundType bound = BoundType.Open)
{
bool inside = value < upperBound && value > lowerBound;
switch (bound)
{
case BoundType.Open:
return inside;
case BoundType.Closed:
return inside || AreEqual(value, upperBound) || AreEqual(value, lowerBound);
case BoundType.OpenClosed:
return inside || AreEqual(value, upperBound);
case BoundType.ClosedOpen:
return inside || AreEqual(value, lowerBound);
default:
throw new System.NotImplementedException("You forgot to do something");
}
}
Maintenant, cela peut être beaucoup plus que ce que vous vouliez, mais cela vous empêche de traiter avec des arrondis tout le temps et d'essayer de vous rappeler si une valeur a été arrondie et à quel endroit. Si vous en avez besoin, vous pouvez facilement étendre cette fonctionnalité à n'importe quel epsilon et permettre à votre epsilon de changer.
Je cherchais un moyen élégant de le faire où les limites pourraient être inversées (c.-à-d. Pas sûr de l'ordre dans lequel les valeurs sont).
Cela ne fonctionnera que sur les versions les plus récentes de C # où le?: Existe
bool ValueWithinBounds(float val, float bounds1, float bounds2)
{
return bounds1 >= bounds2 ?
val <= bounds1 && val >= bounds2 :
val <= bounds2 && val >= bounds1;
}
Évidemment, vous pouvez changer les signes = pour vos besoins. Pourrait aussi avoir du style avec le casting. J'ai juste besoin d'un retour float dans les limites (ou égal à)