'Is there a cleaner way to map an object to a class (e.g. using a dictionary)?

public static class SceneToGame{
    public static GameParentServer Server(int scene, Server server){
        switch (scene){
            case 1:
                return new SnapGameServer(server);
            default:
                return null;
        }
    }
}

I'm currently using this to map ints to classes with a common parent. Is there a cleaner way to do this? From what I've read you can't because C# is type safe despite this being type safe.



Solution 1:[1]

Applying the changes suggested by @clemens and @richard-deeming in comments on the original post, here is a possible implementation; using an enum rather than int for scene, and a switch expression (available from C# 8 [docs]) rather than a switch statement.

public enum SceneType
{
    Undefined, //Undefined = 0,
    Snap,      //Snap = 1,
    Ultra,     //Ultra = 2,
    Ninja      //Ninja = 3
}

As pointed out by @oliver in the comments to this answer, adding a default value (in this example, Undefined) in your enum will ensure that if any SceneType variable in your code is not populated, the value of that variable will default to Undefined rather than e.g. Snap.

Unless you really need to make sure that SceneType.Snap has the numerical value of 1 (and so on), your enum does not need to define specific numerical values for each enum value.

public static GameParentServer Server(SceneType scene, Server server)
{
    return scene switch
    {
        Snap  => new SnapGameServer(server),
        Ultra => new UltraGameServer(server),
        Ninja => new NinjaGameServer(server),
        _ => null
    }
}

Solution 2:[2]

One potential method might be to use reflection to enable intelligent mapping to the GameParentServer child. The primary benefit of this method is that it will use a few lines of codes, and won't require changes to the mapping for every new value of 'scene'. This fits my definition of clean, although opinions can vary.

Edit: As @JHBonarius has pointed out, it's worth considering that there is a performance hit when using this approach. Based on the name of your class - GameServerParent - my presumption is that this is a heavy object that will be initialised only once per application execution. In this scenario, the performance hit here is perfectly acceptable. However, for smaller objects that are constantly initialised, this approach might degrade performance too heavily to be useable. We can discuss implementing a cache of this information if your GameServerParent objects are something you intend to initialise constantly.

To implement this approach, we simply add an identifier attribute to the relevant GameParentServer child classes.

public class GameServerAttribute: Attribute
{
    public GameServerAttribute(int gameServerId)
    {
        GameServerId = gameServerId;
    }

    private int GameServerId { get; }
}

Then, we add to this to the classes that inherit GameParentServer:

[GameServer(1)]
public class SnapGameServer : GameParentServer
{
    public SnapGameServer(Server server) : base(server)
    {
    }
}

Now, we just need to modify the mapping to actually use the attribute and call the constructor via reflection:

public static class SceneToGame{
    public static GameParentServer Server(int scene, Server server)
    {
        Type types = Assembly
            .GetCallingAssembly()
            .GetTypes()
            .FirstOrDefault(t => t.GetCustomAttributes()
                                     .Contains(new GameServerAttribute(scene)) 
                                 && t.IsSubclassOf(typeof(GameParentServer)));

        return (GameParentServer) types?
            .InvokeMember("", BindingFlags.CreateInstance, null, null, new object?[] {server});
    }
}

This code identifies the assembly that is calling the code, retrieves the types in the assembly, and then checks if they have an instance of our new custom attribute with the passed value. If so, we take the first one, and then invoke the constructor, passing in the server parameter.

You are now free to add new [GameServer(scene)] attributes to any class, and a passed int will automatically map to the relevant class. Please keep in mind that my example does not handle construction exceptions, or classes that have duplicate scene values. I also would recommend using an enum instead of an int for mapping, as it will be easier to read and maintain.

One last note, my example here makes the assumption that the classes that inherit GameParentServer are all in the same assembly, and that this assembly is making the call to this code. If you have separated them out, you'll need to retrieve the types of the different assemblies in order to find the applicable types.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2