Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get instance from a dictionary of types and instances

I have a simple service manager called ServiceManager that has two methods. Create() creates an instance of a service. Provide() returns a service that has previously been created.

I have a basic implementation that works but am wondering if there's a cleaner way. This is my basic implementation of the ServiceManager:

public class ServiceManager : MonoBehaviour
{
    private Dictionary<Type, MonoBehaviour> services = new Dictionary<Type, MonoBehaviour>();

    public void Create<T>() where T : MonoBehaviour
    {
        // Create service
        GameObject serviceObject = new GameObject(typeof(T).Name);
        serviceObject.transform.SetParent(transform); // make service GO our child
        T service = serviceObject.AddComponent<T>(); // attach service to GO

        // Register service
        services.Add(typeof(T), service);
    }

    public T Provide<T>() where T : MonoBehaviour
    {
        return (T)services[typeof(T)]; // notice the cast to T here
    }
}

Using the service is simple:

public class ServiceTest : MonoBehaviour
{
    private void Start()
    {
        // Creating services
        ServiceManager services = FindObjectOfType<ServiceManager>();
        services.Create<MapService>();
        services.Create<InteractionService>();
    }

    private void Example()
    {
        // Get a service
        ServiceManager services = FindObjectOfType<ServiceManager>();
        MapService map = services.Provide<MapService>();
        // do whatever you want with map
    }
}

My question is about ServiceManager.Provide(). Notice the cast to T after getting the item from the dictionary. This feel very unclean and makes me wonder if I am missing something about how generics work in C#. Are there other/better ways to do what I am trying to accomplish?

like image 627
Vapid Linus Avatar asked Apr 21 '17 14:04

Vapid Linus


2 Answers

There is nothing to improve here. The cast is necessary because the dictionary value type is a MonoBehaviour. You know that it is actually T, but the compiler doesn't. You have to tell that by casting.

You did well.

like image 195
Patrick Hofman Avatar answered Oct 13 '22 00:10

Patrick Hofman


If there is only ever one instance per type, then there is better. Consider a static generic type

using UnityEngine;

public class ServiceManager : MonoBehaviour
{
    // If this T confuses you from the generic T used elsewhere, rename it
    public static Transform T { get; private set; }

    void Awake()
    {
        T = transform;
    }

    public T Provide<T>() where T : MonoBehaviour
    {
        return ServiceMap<T>.service; // no cast required
    }
}

static class ServiceMap<T> where T : MonoBehaviour
{
    public static readonly T service;

    static ServiceMap()
    {
        // Create service
        GameObject serviceObject = new GameObject(typeof(T).Name);
        serviceObject.transform.SetParent(ServiceManager.T); // make service GO our child
        service = serviceObject.AddComponent<T>(); // attach service to GO
    }
}

Using the service is simple:

public class ServiceTest : MonoBehaviour
{
    private void Start()
    {
        // no need to Create services
        // They will be created when Provide is first called on them
        // Though if you want them up and running at Start, call Provide
        // on each here.
    }

    private void Example()
    {
        // Get a service
        ServiceManager services = FindObjectOfType<ServiceManager>();
        MapService map = services.Provide<MapService>();
        // do whatever you want with map
    }
}

Also, if you have multiple ServiceManagers then this won't work.

like image 36
Chuck Savage Avatar answered Oct 12 '22 22:10

Chuck Savage