0

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.

4

1 回答 1

1

嗯.. 有一个真正的问题,用户可以创建他的 AppDomains。在当前进程中没有很好的方法来获取所有加载的 AppDomain。我使用在网络中建立的坏黑客:Hot to get list of all AppDomains inside current process?. 结果代码:

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";
    }

}

我们做什么:我们遍历所有 AppDomain,从它们那里获取所有组件。对于每个程序集,我们尝试查找类型Key + "Role"(基于约定),检查没有问题,获取“实例”字段。

现在,关于 hack:这是个坏主意。更好的是,如果您将创建包含所有已加载域列表的单例。在创建域时,您必须将其添加到列表中,在卸载时 - 从列表中删除并从角色和其他类中删除属于域的所有类型。现在如何工作:无法卸载 AppDomain,因为其中一种类型始终存在链接。如果您不需要卸载 AppDomains,您可以保留此代码原样。

如果您永远不会创建 AppDomain,则只能遍历AppDomain.CurrentDomain程序集。

于 2013-07-12T19:38:56.487 回答