Question

As part of trying to propose an answer to another question, I wanted to create a Dictionary of self-registering Singleton instances. Specifically, something like this:

public abstract class Role
{
    public static Dictionary<string, Role> Roles = new Dictionary<string, Role>(); 

    protected Role()
    {
        _roles.Add(this.Name, this);
    }
    public abstract string Name { get; }
}

public class AdminRole : Role
{
    public static readonly AdminRole Instance = new AdminRole();
    public override string Name { get { return "Admin"; } }
}

However, the AdminRole constructor isn't being called unless I access Instance, so it's not being added to the Roles dictionary. I know I could just instantiate the dictionary using { AdminRole.Instance.Name, Admin Role}, but I'd like adding new roles to not require the Role class to change.

Any suggestions? Is this even a good design for accessing Singletons by string?


The line of code to test the result is:

var role = Role.Roles["Admin"];

It's successful if you don't get a KeyNotFound exception (or null).

There can be an explicit initialization of Role (such as Role.Initialize()), but not of the subclasses - the idea is to be able to add a subclass so the dictionary has it, without ever needing to change anything pre-existing.

Was it helpful?

Solution

Hm.. There is real problem, that user can create his AppDomains. There are no good way to get all loaded AppDomains in current process. I use bad hack founded in network: Hot to get list of all AppDomains inside current process?. Result code:

public static void Main()
{
    Console.WriteLine(Role.GetRole("Admin").Ololo);
}

public static class AppDomainExtensions {
    public static List<AppDomain> GetAllAppDomains() {
        List<AppDomain> appDomains = new List<AppDomain>();

        IntPtr handle = IntPtr.Zero;
        ICorRuntimeHost host = (ICorRuntimeHost)(new CorRuntimeHost());
        try
        {
            host.EnumDomains(out handle);
            while (true)
            {
                object domain;
                host.NextDomain(handle, out domain);
                if (domain == null)
                    break;
                appDomains.Add((AppDomain)domain);
            }
        }
        finally
        {
            host.CloseEnum(handle);
        }

        return appDomains;
    }

    [ComImport]
    [Guid("CB2F6723-AB3A-11d2-9C40-00C04FA30A3E")]
    private class CorRuntimeHost// : ICorRuntimeHost
    {
    }

    [Guid("CB2F6722-AB3A-11D2-9C40-00C04FA30A3E")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface ICorRuntimeHost
    {
        void CreateLogicalThreadState ();
        void DeleteLogicalThreadState ();
        void SwitchInLogicalThreadState ();
        void SwitchOutLogicalThreadState ();
        void LocksHeldByLogicalThread ();
        void MapFile ();
        void GetConfiguration ();
        void Start ();
        void Stop ();
        void CreateDomain ();
        void GetDefaultDomain ();
        void EnumDomains (out IntPtr enumHandle);
        void NextDomain (IntPtr enumHandle, [MarshalAs(UnmanagedType.IUnknown)]out object appDomain);
        void CloseEnum (IntPtr enumHandle);
        void CreateDomainEx ();
        void CreateDomainSetup ();
        void CreateEvidence ();
        void UnloadDomain ();
        void CurrentDomain ();
    }   
}

public abstract class Role
{
    private static Dictionary<string, Role> Roles = new Dictionary<string, Role>(); 

    public static Role GetRole(string key) {
        if (Roles.ContainsKey(key))
            return Roles[key];

        foreach (var appDomain in AppDomainExtensions.GetAllAppDomains()) {
            foreach (var assembly in appDomain.GetAssemblies()) {
                var type = assembly.GetTypes().Where(t => t.Name == key + "Role").FirstOrDefault();// (key + "Role", false, true);              

                if (type == null || !typeof(Role).IsAssignableFrom(type))
                    continue;

                Role role = null;

                {
                    var fieldInfo = type.GetField("Instance", BindingFlags.Static | BindingFlags.Public);


                    if (fieldInfo != null) {
                        role = fieldInfo.GetValue(null) as Role;               
                    }
                    else {
                        var propertyInfo = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);

                        if (propertyInfo != null)
                            role = propertyInfo.GetValue(null, null) as Role;
                    }
                }

                if (role == null)
                    continue;

                Roles[key] = role;

                return role;
            }           
        }

        throw new KeyNotFoundException();
    }

    public string Ololo {get;set;}
}

public class AdminRole : Role
{
    public static readonly AdminRole Instance = new AdminRole();

    private AdminRole() {
        Ololo = "a";
    }

}

What we do: we iterate over all AppDomains, getting from them all Assemblies. For each Assembly we try to find type Key + "Role" (convention based), checking that there are no problems, get "Instance" field.

Now, about hack: it's bad idea. Better, if you will create singleton with list of all loaded domains. On creating of domain you must add it to list, on unloading - remove from list and remove all types that belongs to domain from Roles and another classes. How it works now: there are no possibility to unload AppDomain, because there always is a link on one of it's types. If you don't need to unload AppDomains, you can leave this code as it wrotten.

If you never will create AppDomain, you can iterate only throught AppDomain.CurrentDomain assemblies.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top