I’m finishing an unfinished C# WinForms project, which is a save game editor for an unspecified game. I noticed that character portraits and icons’ IDs have a valid range of 1 to 905, but there was no code to verify that the character ID is within that range, so I opted to fix it. I have an in-progress solution to this, with the code for it (with a lot of function names partially redacted) at the end of this post, and wanted to know if it would work. Visual Studio says there are no formatting errors, but I know that alone will not quite be enough. I cannot compile to test because I am not quite at a stage where the code is compilable, with a lot of missing WinForms data. If the specific exception that I placed would not be appropriate, what exception would best fit? If a while loop should not be used in this way, what method should I use that doesn’t require rewriting the entire code block from scratch? Here is the relevant code I have so far:
void getImage(int slot)
{
short ID = 0;
if (slot == 1)
{
try
{
ID = convertStringtoID(comboBoxSlot1ID.Text);
}
catch (System.FormatException)
{
ID = 0;
}
while (ID > 0 && ID < 906)
{
if (ID < 10)
{
pictureBoxSlot1Portrait.Image = (Image)Properties.Resources.ResourceManager.GetObject("00" + ID);
pictureBoxSlot1Icon.Image = (Image)Properties.Resources.ResourceManager.GetObject("dot00" + ID);
}
else if (ID > 9 && ID < 100)
{
pictureBoxSlot1Portrait.Image = (Image)Properties.Resources.ResourceManager.GetObject("0" + ID);
pictureBoxSlot1Icon.Image = (Image)Properties.Resources.ResourceManager.GetObject("dot0" + ID);
}
else if (ID > 99 && ID < 906)
{
pictureBoxSlot1Portrait.Image = (Image)Properties.Resources.ResourceManager.GetObject("" + ID);
pictureBoxSlot1Icon.Image = (Image)Properties.Resources.ResourceManager.GetObject("dot" + ID);
}
else
{
try { }
catch (System.IndexOutOfRangeException)
{
ID = 0;
}
}
}
}
}
I have not yet been able to try or test anything yet; this is merely a question regarding the valid use of C# code. I also saw just 1 question that was similar, but not the same, as my question in the ‘possibly related questions’ section before posting this, although it did partially indicate that what I am doing may be okay. I am expecting the code to throw IndexOutOfBoundsException
if ID
is greater than 905, and read the proper image file if not.
3
Answers
Take note of this structure:
Notice how the
try
block wraps an operation which could potentially fail. Contrast that with this structure:What operation within that
try
block might fail and why?Whatever operation might fail would go within the
try
block. If that operation is an entire loop then that entire loop would go there.However, what you have here isn’t really a valid use of exceptions in the first place. Don’t rely on exceptions for conditions you can directly check in the code.
It’s worth noting the loop logic:
If
ID
is not within this range then the loop won’t iterate again anyway. Outside the context of this, if you ever want to setID
to0
when it’s not within this range, that’s a simpleif
condition:Or, perhaps within the scope of your specific code, if the goal of that
else
block is to setID
to0
then you can just… setID
to0
:(Note that even the structurally valid try/catch shown earlier can also be replaced with logic. Take a look at the
TryParse
method.)This does nothing and should be removed.
you can just do
ID.ToString("000")
to get a zero-prefixed three digit string for the number.From the ResourceManager.GetObject
So why are you expecting an
IndexOutOfBoundsException
?you are not incrementing the ID inside the loop, so this loop will be infinite.
It is rarely a good idea to hardcode such values, since things like this is often subject to change. You should probably list all the icons in the resource file and present the valid options from some kind of list. Or to just try retrieving whatever the user enters, and inform the user about the failure if it does not exist.
I have no idea about what kind of game you are working with. But in games it is fairly common to separate the "assets" and other resources from the actual game code. When I did Game dev this involved custom build chains and container formats, but I expect that the tooling have been standardized and improved since then. Regardless, there might be better way to manage assets like portraits and icons than the fairly ancient .resx files. A super simple option is to just a bunch of images or other data in a zip-archive, this is essentially how docx and other office files work.
This is a custom method. Rather than wrap it in a try/catch, make it return the default
0
value. While I’m here, that comboBox should also be given as an argument to thegetImage()
method (and the potriat/icon pair returned as a result, maybe in a tuple).I understand you might not have that option, and assuming you have a legitimate need to use it instead of a simple
int.Parse()
orint.TryParse()
, or if it’s used in other places and expected to throw. In that case, I then look at thecatch
value and see this:And then notice this:
Put them together, and we can just exist the method right away.
Looking more at this loop:
Nothing inside that loop ever changes
ID
, so the loop would run forever. You wantif()
instead ofwhile()
.I also see the conditional expressions used to decide how many
0
s you need to prefix the ID value. You can replace all that like this:Another item, for the question about the empty try:
The
try
has nothing in it, least of all anything that would every throw an exception, and therefore thecatch
will NEVER run. It seems like you just want this:And I need caution here about avoiding using exceptions for program logic control flow in the first place. It’s extremely poor practice. Rather, exceptions should reserved for events that are genuinely exception or unexpected.
But really, I’d skip that
else()
completely, since we already know logically from the checks made when entering the block that the value is in the expected range.Finally, I saw this comment:
We can improve on that like this:
And then use the new array variables when needed:
Even better if the arrays are defined for this at the class level, but I’ll leave that change for another time.
Put it all together, and the method reduces to this:
This now works with only about 1/6 the code of the original. That it will also be faster is just a bonus.
There are further improvements that can be made as well, to better decouple the UI from the back end and simplify the code, but this is enough for today. But it’s worth repeating here that at no point is an exception ever thrown or even tested. No try/catch is needed.