7

I declare a variable like this:

public static int aBtn;

But the only valid values are 0,1,2,3,4 and 5

Is there any way that I can avoid any problems with my code later on my limiting it so that something like an exception will happen if I try to set the value to 6.

Note that I still want to be able to do things like:

aBtn = aBtn + 1; 
Alan2
  • 23,493
  • 79
  • 256
  • 450

3 Answers3

17

No. This is a good example of why exposing public fields is a bad idea - you have no control over how they're used.

If you change it into a property, you can validate the value in the setter:

// TODO: Use a better name than either foo or aBtn
private static int foo;

public static int Foo
{
    get => foo;
    set => foo = value >= 0 && value < 6
        ? value
        : throw new ArgumentOutOfRangeException("Some useful error message here");
}

If you don't like using the conditional ?: operator there, you can use a block-bodied setter:

public static int Foo
{
    get => foo;
    set
    {
        if (value < 0 || value > 5)
        {
            throw new ArgumentOutOfRangeException("Some useful error message");
        }
        foo = value;
    }
}

Or better, have a utilty method that validates a value and returns the input if it's in range, or throws an exception otherwise. You can then use something like:

public static int Foo
{
    get => foo;
    set => foo = Preconditions.CheckArgumentRange(nameof(value), value, 0, 5);
}

Here's a slightly modified version of CheckArgumentRange from Noda Time. (The real version has a separate method to do the throwing, which I suspect is for performance reasons, to allow the comparison part to be inlined.)

internal static int CheckArgumentRange(
    string paramName, int value, int minInclusive, int maxInclusive)
{
    if (value < minInclusive || value > maxInclusive)
    {
        throw new ArgumentOutOfRangeException(paramName, value,
            $"Value should be in range [{minInclusive}-{maxInclusive}]");
    }
    return value;
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Great answer. Can you give an example of the utility method you use in this example. Thanks – Alan2 May 18 '19 at 09:58
  • 1
    @Alan2: Done - I've added the parameter name in there as well. – Jon Skeet May 18 '19 at 10:02
  • Thanks, This will make a big difference to me and allow me to put in some really good additional checks. – Alan2 May 18 '19 at 10:03
  • Quick follow up on your comment "exposing public fields is a bad idea". In that same class I have a public field like this: public static ATI ati; where ATI is an enum. Are there risks with that also or is that okay as a public field? – Alan2 May 18 '19 at 10:08
  • 2
    @Alan2: No, I'd still make that private. I view fields as *implementation details* which shouldn't leak out of the class they're declared in. I always use properties for access outside the class. That gives you much better control. (For your enum example, you probably still want to validate that it's never set to an invalid enum value, for example.) In the past I've made exceptions for constants and for "nearly constants" - e.g. readonly variables for immutable types which are initialized at execution time. But even then I've regretted it, and moved to properties (which is a breaking change!) – Jon Skeet May 18 '19 at 10:12
2

Couldn't you just use an enum with your only possible values? It's a good way to define small categorical sets.

For example:

 public enum RangedInt
{
    cero,
    one,
    two,
    three,
    four,
    five,
    six = 6
}
public class MyClass
{
    public RangedInt Field { get; set; }
}
  • Can you please add an example? Please make sure you support the +1 case. – Tomer Shetah Aug 23 '20 at 07:22
  • @TomerShetah i have added the example requested. Thank you for the support :) I hope this help – eestevanell Sep 04 '20 at 22:34
  • @eestevanell Unfortunately with Enums it is possible to set the value to say `(RangedInt)7`, it will not throw an exception, and when using `((RangedInt)7).ToString()` the result will be '7' instead of a named field. –  Feb 05 '23 at 06:40
0

Sure. Use an auxiliary variable to test for the values you wish, then, if the tests go OK, pass the value to the real variable.

I think this idea is better expressed through a code. Modify it according to your wishes:

private static int aBtn;

public static int aBtnTest
{
  get
   {
    return aBtn;
   }
  set
   {
    if ((value<0)||(value>6))) //here you test for the values you want
    //do something
   }
}

When you want to use your variable, you do all the processing using aBtnTest, not aBtn. By doing so, all the comparisons are made automatically by the compiler and you do not have to worry anymore about it.

Bogdan Doicin
  • 2,342
  • 5
  • 25
  • 34