如何在C#中强制将一个数字限制在特定范围内?

46
在C#中,我经常需要将整数值限制在一定范围内。例如,如果应用程序期望一个百分比,那么来自用户输入的整数必须不小于零且不大于一百。另一个例子:如果有五个网页可以通过Request.Params["p"]访问,我期望的值是1到5之间的值,而不是0、256或99999等其他值。
我通常最终编写出相当丑陋的代码,类似于:
page = Math.Max(0, Math.Min(2, page));

甚至更难看:

percentage =
    (inputPercentage < 0 || inputPercentage > 100) ?
    0 :
    inputPercentage;

有没有更聪明的方法可以在 .NET Framework 中完成这样的事情?

我知道我可以编写一个通用方法 int LimitToRange(int value, int inclusiveMinimum, int inlusiveMaximum) 并在每个项目中使用它,但也许框架中已经有一个神奇的方法了吗?

如果我需要手动实现,那么在第一个示例中我正在做的“最佳”(即更少丑陋且更快)方式是什么?类似于这样的东西?

public int LimitToRange(int value, int inclusiveMinimum, int inlusiveMaximum)
{
    if (value >= inclusiveMinimum)
    {
        if (value <= inlusiveMaximum)
        {
            return value;
        }

        return inlusiveMaximum;
    }

    return inclusiveMinimum;
}

9
通常被称为“clamp”:请参见https://dev59.com/m3RB5IYBdhLWcg3wxZ_Y和https://dev59.com/lnRC5IYBdhLWcg3wCMrX和https://dev59.com/BHE85IYBdhLWcg3wpFEa等等。 - ShreevatsaR
8个回答

73

这个操作被称为“夹紧(Clamp)”,通常像这样书写:

public static int Clamp( int value, int min, int max )
{
    return (value < min) ? min : (value > max) ? max : value;
}

3
我喜欢“Clamp”这个名字。甚至Microsoft.Xna.Framework也有一个Clamp方法 :) - Syd
3
夹具(Clamp)是正常的名称。请参阅OpenCL规范。 - IamIC
8
这段代码使用 <= 和 >= 操作符比使用 < 和 > 操作符更高效。 :-) - IamIC
2
添加“this”关键字,就可以将其用作扩展方法:public static int Clamp(this int value, int min, int max ) { return (value < min) ? min : (value > max) ? max : value; }现在你可以这样说:myInt.Clamp(10,50); - Molecool
2
从.NET Standard 2.1(Core 2.0)开始,内置了Math.Clamp()方法。 - Kuba Szostak

36

以下是一种更干净的方法,适用于不仅仅是整数的情况(摘自我共享代码库):

public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
    if (value.CompareTo(min) < 0)
        return min;
    if (value.CompareTo(max) > 0)
        return max;

    return value;
}

12
如果valuenull,那么会引发空引用异常(NullReferenceException)。最好使用一个IComparer<T>(例如Comparer<T>.Default)来代替。http://msdn.microsoft.com/en-us/library/azhsac5f.aspx - dtb
1
从C#8开始,您可以在IComparable<T>后添加notnull以避免dtb指出的异常。 public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>, notnull { // your choice of clamp method body } - Jacob Huckins
这非常精妙和优雅,因为它不仅涵盖了可比较的数字类型(例如DateTime),而且还包括其他类型! - David Liebeherr
1
你可以添加一个检查来确保min不大于max。 - David Liebeherr
这是稍微改进过的版本: https://pastebin.com/0N0PaG6a - David Liebeherr

32

我看到了Mark的回答并通过this进行了提升:

public static class InputExtensions
{
    public static int LimitToRange(
        this int value, int inclusiveMinimum, int inclusiveMaximum)
    {
        if (value < inclusiveMinimum) { return inclusiveMinimum; }
        if (value > inclusiveMaximum) { return inclusiveMaximum; }
        return value;
    }
}

使用方法:

int userInput = ...;

int result = userInput.LimitToRange(1, 5)

参见:扩展方法


4
请注意,使用扩展方法并不一定是件好事。它只是另一种编程工具,并且有其用途。如果将其放在错误的位置或上下文中,你最终会编写出难以阅读的代码。 - Trap
1
你的回答应该只是一条评论,建议改进他的回答。 - MMalke

17

我喜欢Guffa的答案,但我很惊讶还没有人使用Min/Max提供解决方案。

public int LimitInclusive(int value, int min, int max)
{
    return Math.Min(max, Math.Max(value, min));
}

每次使用 MinMax 会不会影响性能? - Arseni Mourzenko
因为这仅支持整数和可以隐式转换为整数的类型。 - Orestis P.
感谢您发布这篇文章。在我的系统上,它确实快得多,如果您可以处理 int 数据类型,应该使用它。我相信这是无分支的,并且将使用处理器上的数学优化指令。 - John Evans
1
@OrestisP:这正是OP所需要的。你编写的每个函数都不需要能够处理你能想到的任何用例。那样做只会浪费大量时间来构建一个过度工程化的混乱。 - Ed S.
实际上,Math.Max(以及Math.Min)有10个重载;它可以处理整数(intlonguint等)和浮点数(floatdouble)。因此,如果OP还需要处理浮点数,他们可以重载LimitInclusive - pepoluan
@Ed S. 同意,幸运的是你所说的在这种情况下并不适用。 - Orestis P.

8

编写LimitToRange函数的另一种方法如下。

public int LimitToRange(int value, int inclusiveMinimum, int inclusiveMaximum)
{
    if (value < inclusiveMinimum) { return inclusiveMinimum; }
    if (value > inclusiveMaximum) { return inclusiveMaximum; }
    return value;
}

我认为这种方法更容易理解,同时还很高效。

3
如果值在范围内,无论语句的顺序如何,都必须执行两个检查。就性能而言,所有答案应该是相等的。 - dtb
2
使用 <= 和 >= 可以避免在边缘情况下进行第二次检查或跳转 :-) - IamIC

3
不给用户任何错误反馈的情况下夹紧数值,一般来说可能不是一个好主意(在我看来)。这可能会导致难以调试的微妙错误,特别是当最小/最大值在运行时确定时。想象一下,你的银行账户里有100美元,你想转账150美元给你的朋友。你希望你的银行系统抛出“余额不足异常”,还是与你的朋友讨论你转账了150美元,但他只收到了100美元(假设银行夹紧了转账金额为100,因为你没有足够的资金)?
话虽如此,你也应该看一下代码合同。
public void MyFunction (Type input)
{
   Contract.Requires(input > SomeReferenceValue);
   Contract.Requires (input < SomeOtherReferencValue);

}

这将强制用户输入在一定范围内。

3

没有在框架中内置这个功能的方法。我想这是因为你已经有了MinMax,所以你可以用它们来实现。

如果你自己编写这个方法,那么不管你如何编写,都不会有太大的影响。如果你使用if语句或条件运算符?,它最终编译成的代码都是基本相同的。


3
如果我们“已经有[工具]”,这听起来像是停止所有新的和/或更新编程语言方面的努力的论据!!……我们有工具来编写自己的Min Max函数,那为什么他们还会给我们那些工具? - noelicus

1

我喜欢 Clamp 这个名字。我建议以下的类别:

public class MathHelper
{
    public static int Clamp (int value,int min,int max)
    {
          // todo - implementation 
    }
    public static float Clamp (float value,float min,float max)
    {
          // todo - implementation 
    }
)

或者如果你想使用泛型,那么

public class MathHelper
{
     public static T Clamp<T> (T value, T min, T max) where T : IComparable
     {
         // todo - implementation
         T output = value;
         if (value.CompareTo(max) > 0)
         {
             return max;
         }
         if (value.CompareTo(min) < 0)
         {
            return min;
         }
        return  output;
     } 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接