【wif系列】C#之单例模式(Singleton Pattern)最佳实践
目录
前言
在上一篇译文——《深入理解c#——在c#中实现单例模式》中,对在c#中实现单例模式进行了详细阐述。我们在日常的开发中可以采用解决方案4或解决方案6来实现单例模式,但每个单例类都需要单独实现。
我们再来看看使用单例模式的一些场景:
主要意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
使用场景:
- 要求生产唯一序列号;
- web 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来;
- 创建的一个对象需要消耗的资源过多,比如 i/o 与数据库的连接等;
- 全局配置文件访问,单例类来保证数据唯一性;
- 日志记录帮助类,为节省资源,全局一个实例一般就够了;
- 桌面应用常常要求只能打开一个程序实例或一个窗口。
单例基类
可以看到单例模式在程序开发中是非常常见的。既然我们会频繁的使用单例模式,那么有没有什么方式可以更方便的生产我们的单例。当然有,我们往下看。
对于没有基类的一些类的单例模式实现,可以考虑继承自单例基类。由单例基类派生的类必须是密封类,它确保您不能从这个单例类创建子类。单例的生产就由基类来完成,派生类只需要定义一个无参数的私有构造函数即可,它确保不能在外部创建此类的实例。通过调用继承的实例属性访问类的单例实例和公共成员。
/// <summary> /// 总括来说,为了使用单例基类创建单例类,您需要执行以下操作: /// /// 1) 定义一个派生自singletonbase [t]的密封类,其中t是您定义的类名。 它确保您不能从此单例类创建子类。 /// 2) 在类中定义一个无参数的私有构造函数。它确保不能在外部创建此类的实例。 /// 3) 通过调用instance属性来访问类的单例实例和公共成员。 /// /// </summary> /// <typeparam name="t"></typeparam> public abstract class singletonbase<t> where t : class { #region properties /// <summary> /// 获取该类的单例实例。 /// </summary> [suppressmessage("microsoft.design", "ca1000:donotdeclarestaticmembersongenerictypes")] public static t instance => singletonfactory.instance; #endregion #region constructors #endregion /// <summary> /// 创建单例实例的单例类工厂。 /// </summary> private class singletonfactory { #region fields /// <summary> /// 定义弱引用实例。 /// </summary> private static weakreference _instance; #endregion #region properties /// <summary> /// 获取实例。 /// </summary> internal static t instance { get { if (!(_instance?.target is t comparer)) { comparer = getinstance(); _instance = new weakreference(comparer); } return comparer; } } #endregion #region constructors /// <summary> /// 防止编译器生成默认构造函数。 /// </summary> private singletonfactory() { } /// <summary> /// 显式静态构造函数,告诉c#编译器不要将类型标记为beforefieldinit。 /// </summary> [suppressmessage("microsoft.performance", "ca1810:initializereferencetypestaticfieldsinline")] static singletonfactory() { } #endregion #region methods /// <summary> /// 获取特定类型的实例。 /// </summary> /// <returns>the <see cref="t"/></returns> [suppressmessage("microsoft.reliability", "ca2001:avoidcallingproblematicmethods", messageid = "system.type.invokemember")] private static t getinstance() { var thetype = typeof(t); t inst; try { inst = (t) thetype.invokemember(thetype.name, bindingflags.createinstance | bindingflags.instance | bindingflags.nonpublic, null, null, null, cultureinfo.invariantculture); } catch (missingmethodexception ex) { throw new typeloadexception( string.format(cultureinfo.currentculture, "the type '{0}' must have a private constructor to be used in the singleton pattern.", thetype.fullname), ex); } return inst; } #endregion } }
在singletonbase
当然这里的weakreference
单例提供者
对于有基类的类来说,上面的单例基类显然是不合适的。我们可以考虑实现一个单例提供者来生产我们的单例。
通过泛型类传参的方式实现如下:
/// <summary> /// 用于从另一个类创建或获取单例的静态助手。 /// </summary> /// <typeparam name="t">要创建或获取单例的类型。</typeparam> public static class singletonprovider<t> where t : class, new() { #region fields /// <summary> /// 获取给定类型的单例。 /// </summary> private static readonly lazy<t> _lazy = new lazy<t>(() => new t()); #endregion #region properties /// <summary> /// 获取给定类型的单例。 /// </summary> public static t instance => _lazy.value; #endregion }
除了泛型类传参,还可以通过get方法传参,实现如下:
/// <summary> /// 用于从另一个类创建或获取单例的静态助手。 /// </summary> public static class singletonprovider { #region methods /// <summary> /// 获取指定类型的单例。 /// </summary> /// <typeparam name="tparameter">单例类型。</typeparam> /// <returns>the <see cref="tparameter" />单例对象。</returns> public static tparameter get<tparameter>() where tparameter : class, new() { return singletonprovider<tparameter>.instance; } #endregion }
总结
有了以上两种生产单例的方式,我们可以在开发中愉快的使用单例,而免除了具体的繁琐实现。
wif 项目代码:https://github.com/leoyang610/wif