Prevent a byte from wrapping back to 0 when incremented over 255
I'm writing an XNA 3.1 application for a C#/XNA class I'm taking right now. It's a very simple introductory assignment where the user simply changes the color of the screen, pressing the R, G, or B buttons on the keyboard to choose a color channel and then the up and down arrows to increase or decrease the value of that channel.
If I increment a byte past 255
it wraps back to 0
, and vice versa for decrementing, presumably because C# actually converts a byte
to an int
when doing arithmetic on them. However, my professor specifically asks that once a channel reaches its maximum or minimum value that it stays there if the user tries to go out of these bounds. I figured out a way to fix this by myself, but I didn't find anything when I searched for solutions to this kind of problem on here or on Google. This is how I've solved the problem here:
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
// ...
Color color;
private int Red
{
set { color.R = NoWrapIntToByte(value); }
get { return (int)color.R; }
}
private int Green
{
set { color.G = NoWrapIntToByte(value); }
get { return (int)color.G; }
}
private int Blue
{
set {color.B = NoWrapIntToByte(value); }
get { return (int)color.B; }
}
/// <summary>
/// Converts an integer to a byte but doesn't wrap.
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
static byte NoWrapIntToByte(int x)
{
if (x < Byte.MinValue)
return Byte.MinValue;
else if (x > Byte.MaxValue)
return Byte.MaxValue;
else
return (byte)x;
}
}
Every time the Draw()
method is called, the color stored in color
is set as the color of the screen.
Just for clarity, I realize I can't do something like
byte x = 256; // compiler error
the compiler will complain. However, when the user presses the up or down arrows that color channel is incremented or decremented, and if the channel is decremented below 0 or incremented above 255 it will wrap. I used int
as the type of the channel properties because if I'm using something like Red -= 1
or开发者_如何学C Red += 1
there are different expectations that happen if I go out of bounds. If I'm adding I would expect the resulting value to be smaller than the original value, and vice versa if I'm decrementing. I'm using int
because it allows me to detect this in the property and handle accordingly.
I think my solution is okay, but I'm curious if I'm overlooking something. I can't be the only person who has tried to solve this problem so I'm curious what an idiomatic or best-practice way to solve this problem would be. I'm not asking for answers to my homework problem since I do already have a solution that works. However, I don't know C# that well so I'm curious if there's a much simpler way of doing this same thing, or with less code than what I have here. I'm aware that I can make C# throw an Overflow
exception, but that requires changing project settings, and it also doesn't tell me specifically what kind.
Thanks!
Your answer seems like it ought to do what you want, but it feels like a lot of code to get there. If it were my problem to solve, I would probably do something more like this:
static byte NoWrapIntToByte(int x)
{
int tmp;
tmp = Math.Max(x, 0);
tmp = Math.Min(255, tmp);
return (byte)tmp;
}
Thanks @Marlon for the Math.Max/Min
fix.
Thanks @Andrew Noyes for the casting fix.
Hope this helps. :)
It sounds to me that you should just be using the "Clamp" method built into the XNA framework.
http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.mathhelper.clamp.aspx
Basically if you want a value to say within 0 and 255 then you would just write something like this.
static byte NoWrapIntToByte(int x)
{
return (byte)MathHelper.Clamp(x, 0, 255);
}
That should pretty much do exactly what it sounds like you're looking for.
Your solution works but is fundamentally the wrong approach. You should have written your property like this:
private int Red
{
get { return (int)color.R; }
set {
if (value < 0 || value > 255) throw new ArgumentOutOfRangeException();
color.R = (byte)value;
}
}
Which lets anybody that uses your class, including yourself, fall into the pit of success. After which you'd have found the proper solution, something similar to this:
private void IncrementRed_Click(object sender, EventArgs e) {
if (this.Color.Red < 255) this.Color.Red += 1;
}
You can then further improve this by having the property setter raise a PropertyChanged event. An event you can subscribe in your UI to set the button's Enabled property to false so it is immediately obvious to the user that clicking the button is no longer useful.
It might be possible to define a new structure wrapping a byte up in a set of overloaded operators and casters, like this.
struct NoWrapByte
{
private byte _data;
public NoWrapByte(byte data)
{
this._data = data;
}
public static NoWrapByte operator +(NoWrapByte a, NoWrapByte b)
{
if (a._data + b._data > 255)
return new NoWrapByte((byte)255);
else if (a._data + b._data < 0)
return new NoWrapByte((byte)0);
else
return new NoWrapByte((byte)(a._data + b._data));
}
public static NoWrapByte operator -(NoWrapByte a, NoWrapByte b)
{
if (a._data - b._data > 255)
return new NoWrapByte((byte)255);
else if (a._data - b._data < 0)
return new NoWrapByte((byte)0);
else
return new NoWrapByte((byte)(a._data - b._data));
}
public static implicit operator byte(NoWrapByte op)
{
return op._data;
}
}
I don't think i's possible to stop it from wrapping. You should use an int16 or an int32. However, you could use an if statement to check if the result would be larger than 255 and then convert it.
精彩评论