如何使用C#和.NET 3.5 / 4优雅地做到这一点?
例如,数字可以在1到100之间。
我知道一个简单的方法就足够了。但是这个问题的关键词是优雅。这是给我的玩具项目而不是生产项目。
这个问题不是关于速度,而是关于代码美。停止谈论效率之类的话题;记得你在向合唱团宣道。
if
不是“巴洛克式”,请不要修复它。
如何使用C#和.NET 3.5 / 4优雅地做到这一点?
例如,数字可以在1到100之间。
我知道一个简单的方法就足够了。但是这个问题的关键词是优雅。这是给我的玩具项目而不是生产项目。
这个问题不是关于速度,而是关于代码美。停止谈论效率之类的话题;记得你在向合唱团宣道。
if
不是“巴洛克式”,请不要修复它。
Answers:
有很多选择:
int x = 30;
if (Enumerable.Range(1,100).Contains(x))
//true
if (x >= 1 && x <= 100)
//true
另外,请查看此SO帖子以获取正则表达式选项。
if
声明更花哨的东西。这肯定实现了...;)
你的意思是?
if(number >= 1 && number <= 100)
要么
bool TestRange (int numberToCheck, int bottom, int top)
{
return (numberToCheck >= bottom && numberToCheck <= top);
}
只是为了增加噪音,您可以创建一个扩展方法:
public static bool IsWithin(this int value, int minimum, int maximum)
{
return value >= minimum && value <= maximum;
}
那会让你做类似...
int val = 15;
bool foo = val.IsWithin(5,20);
话虽如此,当支票本身仅是一行时,这似乎是一件愚蠢的事情。
正如其他人所说,使用简单的if。
您应该考虑订购。
例如
1 <= x && x <= 100
比起阅读更容易
x >= 1 && x <= 100
=
而不是==
。它对非等式关系运算符没有帮助-但很容易习惯于一致地使用它。
x
是一个复杂的函数调用或耗时的Linq表达式。在这种情况下,您将执行两次,这不是一件好事。当然,您应该将值存储到临时局部变量中,但是在某些情况下(例如,在else-if语句中),您只想在其他if或else-if失败之后调用函数。对于临时变量,您必须始终调用它们。在这些情况下,扩展方法(在其他答案中提到)是最好的解决方案。
在生产代码中,我只需写
1 <= x && x <= 100
这很容易理解,也很容易理解。
这是一个聪明的方法,它通过使用一些数学运算将比较次数从两个减少到一个。想法是,如果数字超出范围,则两个因素之一变为负数;如果数字等于界限之一,则两个因数变为零:
如果范围包括边界:
(x - 1) * (100 - x) >= 0
要么
(x - min) * (max - x) >= 0
如果边界是排他的:
(x - 1) * (100 - x) > 0
要么
(x - min) * (max - x) > 0
1 < x && x < 100
的生产性代码。它更容易理解。
1 < x & x < 100
(无&&短路)指示编译器x < 100
无论的结果如何都可以进行评估1 < x
。奇怪的是(由于分支预测),始终执行此简单操作要比有时跳过它要快。
我建议:
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;
}
例子:
45.IsWithin(32, 89)
true
87.2.IsWithin(87.1, 87.15)
false
87.2.IsWithin(87.1, 87.25)
true
当然还有变量:
myvalue.IsWithin(min, max)
它易于阅读(接近人类语言),并且可以与任何可比较的类型(整数,双精度,自定义类型...)一起使用。
易于阅读的代码很重要,因为开发人员不会浪费“大脑周期”来理解它。在漫长的编码过程中,浪费的大脑周期使开发人员更早疲倦,并容易出现错误。
IsInRange. I'm not that keen on Ben's inclusive boolean as that requires a few more brain cycles. It has the advantage that it can be used in any class that that implements IComparer. This is in my Extensions now along with
LiesWithin / LiesInside. Just can't decide which.
NotOutside可以使用,但我不喜欢负面条件
通过一些扩展方法的滥用,我们可以获得以下“优雅”的解决方案:
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!");
}
}
}
enum Inclusive
与价值:Lower
,Upper
,All
。并通过对In
类型的功能一个附加参数enum Inclusive
的默认值Inclusive.All
,更新的To
函数体来处理All
,Lower
,Upper
值:)
if (value > 1 && value < 100)
{
// do work
}
else
{
// handle outside of range logic
}
使用&&
表达式将两个比较联接起来是最简单的方法。如果尝试使用类似的扩展方法,则会遇到是否要包含上限,下限或同时包含两者的问题。一旦开始添加其他变量或更改扩展名以指示所包含的内容,代码就会变得更长且更难阅读(对于大多数程序员而言)。此外,如果您的比较没有道理,Resharper之类的工具会警告您(number > 100 && number < 1
),,如果您使用方法('i.IsBetween(100,1)'),则它们将不会。
我要发表的唯一其他评论是,如果要检查输入以引发异常,则应考虑使用代码协定:
Contract.Requires(number > 1 && number < 100)
这比更为优雅if(...) throw new Exception(...)
,如果有人尝试调用您的方法而没有确保该数字位于第一个范围内,您甚至可能会收到编译时警告。
如果您想编写比简单的代码更多的代码,也许可以:创建一个名为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());
附录:值得注意的是,在实践中,您很少在代码库中“仅检查是否相等”(或<,>)。(除了最琐碎的情况。)纯粹作为示例,任何游戏程序员都将在每个项目中使用类似于以下内容的类别作为基本问题。请注意,在本示例中,它(可能是)使用了内置于该环境中的函数(近似运算)。在实践中,您通常必须认真开发自己的概念,即比较对于实际数字的计算机表示形式,所要处理的情况类型的含义。(甚至不用说,如果您正在做某事,例如控制器,PID控制器之类的事情,整个问题就变得非常重要并且非常困难,这将成为项目的本质。
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;
}
return (value >= Min && value <= Max);
因为所有其他答案不是我发明的,这里只是我的实现:
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));
}
}
}
然后,您可以像这样使用它:
var value = 5;
var start = 1;
var end = 10;
var result = value.IsWithin(start, end, Range.Closed);
编辑:提供了新的答案。当我写这个问题的第一个答案时,我才刚开始使用C#。事后看来,我现在意识到我的“解决方案”很幼稚且效率低下。
我的原始答案:我会选择更简单的版本:
if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }
因为我还没有看到任何其他更有效的解决方案(至少根据我的测试),所以我再说一遍。
也可以在负范围内使用的更好的新方法:
// Returns true if x is in range [min..max], else false
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);
既可以用于正值范围,也可以用于负值范围,默认范围为
1..100(含)并x
用作要检查的数字,后跟由min
和定义的可选范围max
。
范例1:
// Returns true if x is in range [min..max], else false
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);
Console.WriteLine(inRange(25));
Console.WriteLine(inRange(1));
Console.WriteLine(inRange(100));
Console.WriteLine(inRange(25, 30, 150));
Console.WriteLine(inRange(-25, -50, 0));
返回值:
True
True
True
False
True
示例2:使用1到150之间的随机整数列表
// Returns true if x is in range [min..max], else false
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);
// Generate 100000 ints between 1 and 150
var intsToCheck = new List<int>();
var randGen = new Random();
for(int i = 0; i < 100000; ++i){
intsToCheck.Add(randGen.Next(150) + 1);
}
var counter = 0;
foreach(int n in intsToCheck) {
if(inRange(n)) ++counter;
}
Console.WriteLine("{0} ints found in range 1..100", counter);
返回值:
66660 ints found in range 1..100
执行时间:0.016秒
旧收藏的新变化:
public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) {
if (includeBoundaries)
return number <= topOfRange && number >= bottomOfRange;
return number < topOfRange && number > bottomOfRange;
}
在C语言中,如果时间效率至关重要,并且整数溢出将结束,那么可以做到if ((unsigned)(value-min) <= (max-min)) ...
。如果'max'和'min'是独立变量,则(max-min)的额外减法会浪费时间,但是如果可以在编译时预先计算该表达式,或者可以在运行时对其进行一次计算以测试许多相同范围内的数字,即使在值处于范围内的情况下,也可以有效地计算上述表达式(如果很大一部分值将低于有效范围,则使用起来可能会更快,if ((value >= min) && (value <= max)) ...
因为它将提前退出,如果值小于分钟)。
但是,在使用这种实现之前,请先对目标计算机进行基准测试。在某些处理器上,两部分表达式在所有情况下都可能更快,因为两个比较可以独立完成,而在减法和比较法中,必须先完成减法才能执行比较。
这样的事情怎么样?
if (theNumber.isBetween(low, high, IntEx.Bounds.INCLUSIVE_INCLUSIVE))
{
}
使用以下扩展方法(已测试):
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;
}
}
我会做一个Range对象,像这样:
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;
}
}
然后以这种方式使用它:
Range<int> myRange = new Range<int>(1,999);
bool isWithinRange = myRange.IsWithinBoundaries(3);
这样,您可以将其重用于另一种类型。
Range
对象需要使用该CompareTo
方法来比较项目,而不是<
运算符。
IComparable
并且不会使<
操作员超载。
在检查“数字”是否在范围内时,必须清楚您的意思,两个数字相等是什么意思?通常,您应该将所有浮点数包装在所谓的“ε球”中,方法是选择一些较小的值,然后说两个值是否接近,这是同一件事。
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);
有了这两个帮助器,并假设可以将任意数量的对象转换为没有精度要求的双精度数字。您现在需要的只是一个枚举和另一种方法
public enum BoundType
{
Open,
Closed,
OpenClosed,
ClosedOpen
}
另一种方法如下:
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");
}
}
现在,这可能远远超出了您想要的范围,但是这使您无法一直处理舍入并试图记住某个值是否已舍入到什么地方。如果需要,您可以轻松地扩展它以与任何epsilon一起使用并允许您更改epsilon。
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);
}
}
用法
double numberToBeChecked = 7;
var result = numberToBeChecked.IsBetween(100,122);
var结果= 5.IsBetween(100,120);
var结果= 8.0.IsBetween(1.2,9.6);
如果您担心@Daap对接受的答案的评论并且只能传递一次值,则可以尝试以下操作之一
bool TestRangeDistance (int numberToCheck, int bottom, int distance)
{
return (numberToCheck >= bottom && numberToCheck <= bottom+distance);
}
//var t = TestRangeDistance(10, somelist.Count()-5, 10);
要么
bool TestRangeMargin (int numberToCheck, int target, int margin)
{
return (numberToCheck >= target-margin && numberToCheck <= target+margin);
}
//var t = TestRangeMargin(10, somelist.Count(), 5);
我一直在寻找一种优雅的方法来完成可能会切换边界的操作(即不确定值的顺序)。
这仅适用于存在?:的C#的较新版本
bool ValueWithinBounds(float val, float bounds1, float bounds2)
{
return bounds1 >= bounds2 ?
val <= bounds1 && val >= bounds2 :
val <= bounds2 && val >= bounds1;
}
显然,您可以根据需要更改其中的=符号。也可以使用类型转换。我只需要在(或等于)范围内返回浮点数
优雅,因为它不需要您先确定两个边界值中的哪个较大。它还不包含分支。
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 );
}
我不知道,但是我使用这种方法:
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));
}
这就是我可以使用它的方式:
[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);
}
这些是一些可以帮助您的扩展方法
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;
}
如果要验证方法参数,则所有解决方案都不会抛出ArgumentOutOfRangeException并允许轻松/适当地配置包含/不含的最小值/最大值。
这样使用
public void Start(int pos)
{
pos.CheckRange(nameof(pos), min: 0);
if (pos.IsInRange(max: 100, maxInclusive: false))
{
// ...
}
}
我只是写了这些漂亮的函数。它还具有不对有效值进行分支(单个if)的优点。最难的部分是制作适当的异常消息。
/// <summary>
/// Returns whether specified value is in valid range.
/// </summary>
/// <typeparam name="T">The type of data to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <param name="min">The minimum valid value.</param>
/// <param name="minInclusive">Whether the minimum value is valid.</param>
/// <param name="max">The maximum valid value.</param>
/// <param name="maxInclusive">Whether the maximum value is valid.</param>
/// <returns>Whether the value is within range.</returns>
public static bool IsInRange<T>(this T value, T? min = null, bool minInclusive = true, T? max = null, bool maxInclusive = true)
where T : struct, IComparable<T>
{
var minValid = min == null || (minInclusive && value.CompareTo(min.Value) >= 0) || (!minInclusive && value.CompareTo(min.Value) > 0);
var maxValid = max == null || (maxInclusive && value.CompareTo(max.Value) <= 0) || (!maxInclusive && value.CompareTo(max.Value) < 0);
return minValid && maxValid;
}
/// <summary>
/// Validates whether specified value is in valid range, and throws an exception if out of range.
/// </summary>
/// <typeparam name="T">The type of data to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <param name="name">The name of the parameter.</param>
/// <param name="min">The minimum valid value.</param>
/// <param name="minInclusive">Whether the minimum value is valid.</param>
/// <param name="max">The maximum valid value.</param>
/// <param name="maxInclusive">Whether the maximum value is valid.</param>
/// <returns>The value if valid.</returns>
public static T CheckRange<T>(this T value, string name, T? min = null, bool minInclusive = true, T? max = null, bool maxInclusive = true)
where T : struct, IComparable<T>
{
if (!value.IsInRange(min, minInclusive, max, maxInclusive))
{
if (min.HasValue && minInclusive && max.HasValue && maxInclusive)
{
var message = "{0} must be between {1} and {2}.";
throw new ArgumentOutOfRangeException(name, value, message.FormatInvariant(name, min, max));
}
else
{
var messageMin = min.HasValue ? GetOpText(true, minInclusive).FormatInvariant(min) : null;
var messageMax = max.HasValue ? GetOpText(false, maxInclusive).FormatInvariant(max) : null;
var message = (messageMin != null && messageMax != null) ?
"{0} must be {1} and {2}." :
"{0} must be {1}.";
throw new ArgumentOutOfRangeException(name, value, message.FormatInvariant(name, messageMin ?? messageMax, messageMax));
}
}
return value;
}
private static string GetOpText(bool greaterThan, bool inclusive)
{
return (greaterThan && inclusive) ? "greater than or equal to {0}" :
greaterThan ? "greater than {0}" :
inclusive ? "less than or equal to {0}" :
"less than {0}";
}
public static string FormatInvariant(this string format, params object?[] args) => string.Format(CultureInfo.InvariantCulture, format, args);
您在找in [1..100]
什么?那只是帕斯卡。
when (number) { in 0..9 -> println("1 digit") in 10..99 -> println("2 digits") in 100..999 -> println("3 digits") }