65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中单例模式的可重用基类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (21投票s)

2013 年 4 月 4 日

CPOL

3分钟阅读

viewsIcon

139056

本文介绍了一个用于在 C# 中实现单例模式的可复用基类。

引言

让我们面对现实:每个人都喜欢单例!在过去的几年里,我在所有项目中都使用了许多这种小东西。最近,我厌倦了所有的“复制粘贴”,决定为我未来的所有项目编写一个可复用的单例基类。今天,我想与大家分享我的工作成果。

背景

这个基类的目的是将创建新单例的编码工作量减少到绝对最小值。这不仅意味着减少了您的编码工作量,而且还使您的单例更具可读性和可维护性。

这个基类也是线程安全的,这对于单例来说非常重要。

使用代码 

我今天展示的代码由两个类组成

  • SingletonBase 是基类,每个单例都将从此类派生。
  • SingletonExample 是一个非常简单的单例,它展示了如何从基类派生。

就是这样。全部内容。很简单,对吧?现在让我们直接开始!

示例单例 

我将从本末倒置开始。下面的代码展示了一个非常简单的单例类,它派生自我们的神奇单例基类(稍后将展示)。

/// <summary>
/// This class is an example of how to create a singleton based on the singleton base class.
/// </summary>
class SingletonExample : SingletonBase<SingletonExample>
{
  /// <summary>
  /// Gets or sets a string property.
  /// </summary>
  public string SomeString {get; set; }
 
  /// <summary>
  /// Initializes a new instance of the <see cref="Singleton"/> class.
  /// </summary>
  private SingletonExample()
  {
  }
}

以下是您应该从上面的示例中学到的三个要点

  • 单例从我们的基类 SingletonBase 派生。
  • 我们将单例的类名作为类型参数传递 (SingletonBase<SingletonExample>))。
  • 我们定义了一个 private 构造函数。这很重要,因为我们不希望任何人实际实例化我们的单例。

如您所见,单例本身非常简单。所有繁重的工作都由基类完成。因此,您可以专注于实现您一直梦想的所有那些花哨的方法和属性。

单例基类

现在让我们进入好东西:我们的神奇单例基类! 在这里,我们要确保一次只存在我们的单例的一个实例。并且我们希望以线程安全的方式做到这一点。让我们一步一步地实现它

public abstract class SingletonBase<T> where T : class
{
  ...

在这里,我们声明我们的单例基类。如您所见,这是一个模板类(这就是“T”的来源)。这允许我们传递单例的类作为类型参数(参见上面的示例)。

接下来,我们必须回答一个重要的问题:用户如何访问单例的实例?这个问题通常通过提供一个名为“Instance”的属性来解决(非常有创意,对吧?)。此属性返回我们的单例的唯一实例。以下是我们实现它的方法

/// <summary>
/// Static instance. Needs to use lambda expression
/// to construct an instance (since constructor is private).
/// </summary>
private static readonly Lazy<T> sInstance = new Lazy<T>(() => CreateInstanceOfT());

/// <summary>
/// Gets the instance of this singleton.
/// </summary>
public static T Instance { get { return sInstance.Value; } }

我认为上面的代码行值得稍微解释一下。首先,有一个名为“sInstance”的静态成员。它保存了我们的单例的实例,并进行了延迟初始化。这可以通过使用 .NET 4.0 的 Lazy<T> 类轻松实现。并且最好的事情是:Lazy<T> 是完全线程安全的!

第二个要注意的事情是名为“CreateInstanceOfT”的神秘方法。为什么我们需要它?为什么不简单地调用 new?好吧,这是因为我们的单例有一个私有构造函数(如果您忘记了,只需向上滚动一点到我们的 SingletonExample 类)。因此,我们不得不使用一个小技巧:Activator!

/// <summary>
/// Creates an instance of T via reflection since T's constructor is expected to be private.
/// </summary>
/// <returns></returns>
private static T CreateInstanceOfT()
{
  return Activator.CreateInstance(typeof(T), true) as T;
}

借助这个小方法,我们现在可以实例化我们的单例类,尽管它的构造函数是私有的。巧妙,对吧?

整个单例基类

就是这样!现在您已经看到了整个单例基类。这是所有这些位组合在一起组成一个完整的类,准备好复制和粘贴

/// <summary>
/// A base class for the singleton design pattern.
/// </summary>
/// <typeparam name="T">Class type of the singleton</typeparam>
public abstract class SingletonBase<T> where T : class
{
  #region Members

  /// <summary>
  /// Static instance. Needs to use lambda expression
  /// to construct an instance (since constructor is private).
  /// </summary>
  private static readonly Lazy<T> sInstance = new Lazy<T>(() => CreateInstanceOfT());

  #endregion

  #region Properties

  /// <summary>
  /// Gets the instance of this singleton.
  /// </summary>
  public static T Instance { get { return sInstance.Value; } }

  #endregion

  #region Methods

  /// <summary>
  /// Creates an instance of T via reflection since T's constructor is expected to be private.
  /// </summary>
  /// <returns></returns>
  private static T CreateInstanceOfT()
  {
    return Activator.CreateInstance(typeof(T), true) as T;
  }

  #endregion
}

兴趣点 

您可能已经注意到 Lazy<T> 类仅在 .NET 4.0 及更高版本中可用。如果由于某种更高权力而被迫使用低于此版本的 .NET 版本,那么此代码不适合您。对不起!

历史

我根据下面的评论重写了方法 CreateInstanceOfT。正如人们向我指出的那样,当涉及到实例化对象时,Activator 比反射更快。

© . All rights reserved.