skip to Main Content

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


  1. Take note of this structure:

    try
    {
        ID = convertStringtoID(comboBoxSlot1ID.Text);
    }
    catch (System.FormatException)
    {
        ID = 0;
    }
    

    Notice how the try block wraps an operation which could potentially fail. Contrast that with this structure:

    try
    {
    }
    catch (System.IndexOutOfRangeException)
    {
        ID = 0;
    }
    

    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:

    while (ID > 0 && ID < 906)
    

    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 set ID to 0 when it’s not within this range, that’s a simple if condition:

    if (ID <= 0 || ID >= 906)
    {
        ID = 0;
    }
    

    Or, perhaps within the scope of your specific code, if the goal of that else block is to set ID to 0 then you can just… set ID to 0:

    else
    {
        ID = 0;
    }
    

    (Note that even the structurally valid try/catch shown earlier can also be replaced with logic. Take a look at the TryParse method.)

    Login or Signup to reply.
  2. try { }
    catch (System.IndexOutOfRangeException)
    {
          ID = 0;
    }
    

    This does nothing and should be removed.

    "00" + ID
    

    you can just do ID.ToString("000") to get a zero-prefixed three digit string for the number.

    I am expecting the code to throw IndexOutOfBoundsException if ID is greater than 905
    

    From the ResourceManager.GetObject

    Returns Object

    The value of the resource localized for the caller’s current culture settings. If an appropriate resource set exists but name cannot be found, the method returns null.

    So why are you expecting an IndexOutOfBoundsException?

    while (ID > 0 && ID < 906)
    

    you are not incrementing the ID inside the loop, so this loop will be infinite.

    I noticed that character portraits and icons IDs have a valid range of 1 to 905
    

    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.

    Login or Signup to reply.
  3. ID = convertStringtoID(comboBoxSlot1ID.Text);
    

    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 the getImage() 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() or int.TryParse(), or if it’s used in other places and expected to throw. In that case, I then look at the catch value and see this:

    ID = 0
    

    And then notice this:

    while (ID > 0 && ID < 906)
    

    Put them together, and we can just exist the method right away.

    try
    {
        ID = convertStringtoID(comboBoxSlot1ID.Text);
    }
    catch ()
    {
         return;
    }
    

    Looking more at this loop:

    while (ID > 0 && ID < 906)
    

    Nothing inside that loop ever changes ID, so the loop would run forever. You want if() instead of while().

    I also see the conditional expressions used to decide how many 0s you need to prefix the ID value. You can replace all that like this:

    pictureBoxSlot1Portrait.Image = (Image)Properties.Resources.ResourceManager.GetObject($"{ID:000}");
    pictureBoxSlot1Icon.Image = (Image)Properties.Resources.ResourceManager.GetObject($"dot{ID:000}");
    

    Another item, for the question about the empty try:

     else
     {
         try { }
         catch (System.IndexOutOfRangeException)
         {
             ID = 0;
         }
    }
    

    The try has nothing in it, least of all anything that would every throw an exception, and therefore the catch will NEVER run. It seems like you just want this:

    else
    {
        ID = 0;
    }
    

    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:

    slot has a valid range of 1 to 3 … after the section of code I listed is the exact same code posted twice more, only used for slots 2 and 3 instead.

    We can improve on that like this:

    if (slot < 1 || slot > 3) return;
    var portraits = new PictureBox[] {null, pictureBoxSlot1Portrait, pictureBoxSlot2Portrait, pictureBoxSlot3Portrait};
    var icons = new PicutureBox[] {null, pictureBoxSlot1Icon, pictureBoxSlot2Icon, pictureBoxSlot3Icon};
    var combos = new ComboBox[] {null, comboBoxSlot1ID, comboBoxSlot2ID, comboBoxSlot3ID};
    

    And then use the new array variables when needed:

    ID = convertStringtoID(combos[slot].Text);
    // ...
    portraits[slot].Image = (Image)Properties.Resources.ResourceManager.GetObject($"{ID:000}");
    icons[slot].Image = (Image)Properties.Resources.ResourceManager.GetObject($"dot{ID:000}");
    

    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:

    void getImage(int slot)
    {
       if (slot < 1 || slot > 3) return;
    
       var portraits = new PictureBox[] {null, pictureBoxSlot1Portrait, pictureBoxSlot2Portrait, pictureBoxSlot3Portrait};
       var icons = new PicutureBox[] {null, pictureBoxSlot1Icon, pictureBoxSlot2Icon, pictureBoxSlot3Icon};
       var combos = new ComboBox[] {null, comboBoxSlot1ID, comboBoxSlot2ID, comboBoxSlot3ID};
    
       short ID = 0;
       try {
          ID = convertStringtoID(ID = convertStringtoID(combos[slot].Text););
       } catch() { return; }
    
       if (ID <= 0 || ID >= 906) return;
    
       portraits[slot].Image = (Image)Properties.Resources.ResourceManager.GetObject($"{ID:000}");
       icons[slot].Image = (Image)Properties.Resources.ResourceManager.GetObject($"dot{ID:000}");     
    }
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search