【问题标题】:Eager load and store Singleton渴望加载和存储单例
【发布时间】:2013-07-12 17:59:59
【问题描述】:

作为尝试对另一个问题提出答案的一部分,我想创建一个Dictionary 的自注册单例实例。具体来说,是这样的:

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

但是,除非我访问 Instance,否则不会调用 AdminRole 构造函数,因此它不会被添加到 Roles 字典中。我知道我可以使用{ AdminRole.Instance.Name, Admin Role} 来实例化字典,但我想添加新角色以不需要更改Role 类。

有什么建议吗?这甚至是通过字符串访问单例的好设计吗?


测试结果的代码行是:

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

如果没有得到KeyNotFound 异常(或null),则表示成功。

可以显式初始化 Role(例如 Role.Initialize()),但不是子类 - 想法是能够添加子类,以便字典拥有它,无需更改任何已存在的内容。

【问题讨论】:

  • 您不想在Role 中更改Dictionary 的原因 - 您不想对每个新的子类型进行更改,或者您永远不想进行更改?
  • @nsinreal - 前者。我希望能够创建一个新的 Role 类型,而无需做任何其他事情,只需对其进行子类化。
  • 好的,明白了。反射
  • @nsinreal - 最初的提问者不想“扫描整个程序集以查找 IRoles”。我很擅长反射,只要它在Role 中完全独立。
  • 还有另一种方法。您可以从Role 类中提取Name,并按照约定命名使其工作。你用你的方法GetInstance(key) 或者你的代理类替换你的字典。调用 GetInstance 后,您首先检查您的字典。如果没有这样的角色,则使用 GetType(key + "Role") 遍历加载到程序的所有程序集并使用它。

标签: c# design-patterns initialization singleton static-constructor


【解决方案1】:

嗯.. 存在真正的问题,用户可以创建他的 AppDomain。在当前进程中没有很好的方法来获取所有加载的 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,从它们那里获取所有程序集。对于每个 Assembly,我们尝试查找类型 Key + "Role"(基于约定),检查是否没有问题,获取“Instance”字段。

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

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

【讨论】:

  • 哇。这是相当令人印象深刻的。我不认为我会为the other question 提供答案,如果这是让它发挥作用所需要的,但我肯定会在此处链接他。
  • @Bobson 这真的很难看。 Mono 和 LinqPad 等其他东西存在一些问题。这不是官方支持的功能,所以地狱知道会发生什么。尝试考虑创建静态方法FindRoles(assembly)
  • @Bobson 如果可以有很多子单例(至少两个),您可以使用 LoadDomain() 和 UnloadDomain() 以及事件 DomainLoaded、DomainBeforeUnload 创建一些静态类 AppDomainHelper。我希望你明白下一步该做什么。有问题吗?
猜你喜欢
  • 1970-01-01
  • 2013-05-06
  • 1970-01-01
  • 2013-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-29
  • 2016-03-14
相关资源
最近更新 更多