How can you shorten this switch statement?

Updated on November 23, 2016 in [A] C# .Net
Share on Facebook0Tweet about this on Twitter0Share on Google+0Share on Reddit0
3 on November 22, 2016

I am working on a plugin for Unity for an AI system using behavior trees and blackboards. I assume all of you know what a behavior tree is, but not a blackboard (if you do just skip this explanation), so I will tell you what it is:

What’s a blackboard and a blackboard key:
A Blackboard is basically a place to store all your variables to be used inside of the behavior tree. So let’s take a simple AI for example that will only chase something. The blackboard will have a variable with the target, the behavior tree will chase whatever that variable is set to, and you will set the variable in a script.
Blackboards use a system with keys. They are just like variables, except they have their special type, and they are being accessed with a script (kinda like a parameter in Unity’s animation system).

What I am trying to do and the script:
So each blackboard will have a list of keys, and when pressing a button, it opens a small window where you can set some things. So I was kinda stuck with the UI for the default value… I will just paste the code here and then explain why it’s bad:


keyType = (BBKeyType)EditorGUILayout.EnumPopup("Type", keyType);
switch (keyType)

{

case BBKeyType.Object:

EditorGUILayout.LabelField("No current default value setter available for type Object");

break;
case BBKeyType.Integer:

try

{

if (defaultValue == null)

defaultValue = new int();
defaultValue = EditorGUILayout.IntField("Default Value", (int)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = new int();

}

break;
case BBKeyType.Float:

try

{

if (defaultValue == null)

defaultValue = new float();
defaultValue = EditorGUILayout.FloatField("Default Value", (float)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = new float();

}

break;
case BBKeyType.Boolean:

try

{

if (defaultValue == null)

defaultValue = new bool();
defaultValue = EditorGUILayout.Toggle("Default Value", (bool)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = new bool();

}

break;
case BBKeyType.String:

try

{

defaultValue = EditorGUILayout.TextField("Default Value", (string)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = null;

}

break;
case BBKeyType.Vector4:

try

{

if (defaultValue == null)

defaultValue = new Vector4();
defaultValue = EditorGUILayout.Vector4Field("Default Value", (Vector4)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = new Vector4();

}

break;
case BBKeyType.Vector2:

try

{

if (defaultValue == null)

defaultValue = new Vector2();
defaultValue = EditorGUILayout.Vector2Field("Default Value", (Vector2)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = new Vector2();

}

break;
case BBKeyType.Vector3:

try

{

if (defaultValue == null)

defaultValue = new Vector3();
defaultValue = EditorGUILayout.Vector3Field("Default Value", (Vector3)defaultValue);

}

catch (InvalidCastException)

{

defaultValue = new Vector3();

}

break;

}

Explanation about the script:
So some things you need to know first: defaultValue is a variable of type System.Object. In my actual blackboard I set the value using a generic type, so when creating the key, I set the value, and then every time I want to set the value I can use the generic. Only the default one needs to be object, so it can be flexible between all types.
Also keep in mind that it works perfectly fine. This video will show you. I just want to find a better way.

What’s wrong with the script:
Even if you don’t know anything about programming you should know that this is just bad…it’s unreadable, inefficient, and WAYY too long.

Actual question and things I tried:
A thing know would work is to not make that null check, but to also catch a NullReferenceException, since I do the same thing for both that exception and the InvalidCastException. The problem is, that…I could do it using the “when” keyword when catching, but that only exists in C# 6, which Unity doesn’t use! I could upgrade to the 5.5 beta, but that will result in many bugs, since a beta isn’t anywhere close to perfect.
Does anyone here have a better idea for how I can do this?

Some BTW I like to put:
I just put it here to ask if the titles help reading it, since I always make long posts that are kinda hard to follow, so I decided to put it here, because it might be better.

  • Liked by
Reply
2 on November 22, 2016

So in C# 6 you could do:

defaultValue = EditorGUILayout.FloatField("Default Value", (defaultValue as float?) ?? 0);

But that’ll have to wait.
What you can do is putting only one try-catch around the entire switch.

Most importantly, why aren’t you using dictionaries with types?
Then you could do something like:

 class MainClass
 {
  object DefaultValue = null;
 
  Dictionary<BBKeyType, object> TypeValueMapper = new Dictionary<BBKeyType, object>()
  {
   [BBKeyType.Integer] = 0,
   [BBKeyType.Float] = 0f
  };
 
  Dictionary<Type, Func<object, object>> GuiTypeDrawer = new Dictionary<Type, Func<object, object>>()
  {
   [typeof(float)] = (Value) => EditorGUILayout.FloatField("Default Value", (float)Value),
   [typeof(int)] = (Value) => EditorGUILayout.IntField("Default Value", (int)Value)
  };
 
  public MainClass()
  {
   BBKeyType ValueType = BBKeyType.Integer;
   DefaultValue = TypeValueMapper[ValueType];
   DefaultValue = GuiTypeDrawer[DefaultValue.GetType()](DefaultValue);   ValueType = BBKeyType.Float;
   DefaultValue = TypeValueMapper[ValueType];
   DefaultValue = GuiTypeDrawer[DefaultValue.GetType()](DefaultValue);   Console.ReadLine();
  }
 
  enum BBKeyType
  {
   Integer, Float
  }
 
  static void Main(string[] args)
  {
   MainClass MC = new MainClass();
  }
 
  static class EditorGUILayout
  {
   public static float FloatField(string x, float y)
   {
    Console.WriteLine("Float");
    return y + 0.5f;
   }
 
   public static int IntField(string x, int y)
   {
    Console.WriteLine("Int");
    return y + 3;
   }
  }
 }

If you’re having trouble with that solution, you can ping me on discord 😉

And yes, the titles do help.

Devoted
on November 23, 2016

Pretty embarrassing… I thought about it a little more and I don’t need a default value… It was a thing I wanted to add to it (which doesn’t originally exist (I am kind of taking that mechanic from Unreal Engine, and the don’t have it, but I though it could work well), but in a second that it won’t work. But I am still going to try to solve it because I won’t be able to sleep at night if I won’t do it!
[LineSpace]
[LineSpace]
First of all, the reason I don’t put the try-catch block around the whole switch statement is because, while I did catch the same exception, I handled it differently each time.
[LineSpace]
Just as I was typing I had a problem with your solution, I figured out what I had to do… xD
Well, so I will try to walk you through what I did, just in case you are curious (I spent like 3-4 hours on this today…I am going to save it in a .txt file just because of that :P):
AddBBKeyWindow.cs:
So I made those two dictionaries:

private Dictionary<BBKeyType, object> typeValueMapper = new Dictionary<BBKeyType, object>();
 private Dictionary<Type, Func<object, object>> typeActionMapper = new Dictionary<Type, Func<object, object>>();

Since I use the oldschool C# 4, and I had a bunch of types, I made a method to initialize the dictionaries. Of course I had many more than those two types:
// Initialize type action mapper.
 AddTypeActionMapper<int>(EditorGUILayout.IntField);
 AddTypeActionMapper<float>(EditorGUILayout.FloatField);
// Initialize the type value mapper.
 window.typeValueMapper.Add(BBKeyType.Integer, new int());
 window.typeValueMapper.Add(BBKeyType.Float, new float());

And the AddTypeActionMapper method:
void AddTypeActionMapper<TKey>(Func<string, TKey, GUILayoutOption[], TKey> action, params GUILayoutOption[] options)
 {
 typeActionMapper.Add(
 typeof(TKey),
 (value) => action("Default Value", (TKey)value, options)
 );
 }

This method is called when the window is created.
Then of course in OnGUI I drew the fields and set the typeValueMapper the same way you did in the example:
keyType = (BBKeyType)EditorGUILayout.EnumPopup("Type", keyType);
typeValueMapper[keyType] = typeActionMapper[typeValueMapper[keyType].GetType()](typeValueMapper[keyType]);

After that I make a button to create the new key, which calls a method to create the key. For that you need to understand: Whenever you open the window, I use it like a costructor, so I initialize stuff in it. I took a delegate which would be the method to create the key:
private static AddBBKeyWindow window;
 private Action<string, BBKeyType, object> addKeyMethod;
 public static void ShowWindow(Action<string, BBKeyType, object> addKeyMethod)
 {
 window = GetWindow<AddBBKeyWindow>("Add Blackboard Key");
window.addKeyMethod = addKeyMethod;
}

So if I press the button to create a key, I call a method called CreateKey, and all it does is calling the method that creates the key (the one passed as an argument) and closing the window:
void CreateKey()
 {
addKeyMethod(keyName, keyType, typeValueMapper[keyType]);
 window.Close();
 }

The important part is that it passes the name and the type to it…
BlackboardInspector.cs:
(bb = the blackboard edited)
So Blackboard is basically a scriptable object I can instantiate whenever I want to make a new blackboard to be used with a behavior tree. So wanted to write a custom inspector to it. Of course it starts with a button that if I press I open the window for creating a key:
public override void OnInspectorGUI()
{
if (GUILayout.Button("Add Key"))
 {
 AddBBKeyWindow.ShowWindow(AddKey);
 }
}

And since you probably want to know what the AddKey method does, it basically calls one from the blackboard and passes the arguments. The one in the blackboard just adds a new key to a list of keys with the value, type and name specified.
Then I drew the keys:
for (int i = 0; i < bb.keysAmount; i++)
 {
 bb.RemoveKey(bb.keys[i].name);
 GUILayout.Label("Key " + (i + 1) + ":", EditorStyles.boldLabel);
 EditorGUILayout.LabelField("Value", bb.keys[i].value.ToString());
 bb.keys[i].name = EditorGUILayout.TextField("Name", bb.keys[i].name);
 bb.keys[i].type = (BBKeyType)EditorGUILayout.EnumPopup("Type", bb.keys[i].type);
 if (GUILayout.Button("Remove Key"))
 {
 bb.RemoveKey(bb.keys[i].name);
 }
 }

RemoveKey just looks through the array and removes the current element if it has the specified name.
I know I am not supposed to make the keys list public… It’s only like that for testing purposes.
[LineSpace]
I am sorry I made this long comment… It’s just that I am kinda proud of it. I don’t have many people to share it with, and you happened to answer, so I just sent you that… Now I have a 250 lines long script (the window one)…I blame me taking advice from a guy who made a 1000 lines long script. 😉 (It’s actually because I have two full-sized classes in there, and one of them contains the major switch statement. :P). The amazing thing is that I spent 4 HOURS on making it (no breaks, too), and I am about to delete it…or put it in a .txt file just to remember it. I will probably put it in a .txt file.
But even if it looks like a waste, it was still an amazing exercise. It might seem simple, but implementing into my product was really hard. But it’s extremely satisfying to go into the Unity editor and see it work, not get any exceptions (I am already used to getting exceptions that are being like “You thought you got rid of us, right? NOPE.”), AND know the way I did it isn’t some huge switch statement.
I also learnd a lot (your solutions always help me a lot. Like, the one with the interfaces, it might have seemed like a simple answer for you, but it really changed the way I used interfaces, in a positive way. They don’t help me in terms of knowledge, they just make me use my knowledge in a better way.), so it’s very important.
Again, it might seem like a small solution you give every day, but believe it or not, it was really useful today.
[LineSpace]
Again, I am sorry for the paragraph… It’s just that those blue switches in my keyboard make such an amazing sound! >_<
And sorry it’s messy. Though this time it’s the forum not letting me put line spaces in replies.
[LineSpace]

[LineSpace]
[LineSpace]
Just as a side note…I noticed you used a dictionary initializer… That as well doesn’t exist in C# 4. It’s killing me. I really can’t wait for 5.5 to get out of beta. 😛

Guru
on November 23, 2016

About the initializer… I was teasing you 😉
The dictionaries with delegates are very elegant. You can do everything with them!
I liked reading your post, don’t worry that it’s too long 😉
 
I’m glad I could help!

Show more replies
  • Liked by
Reply
Cancel