关于单例与静态构造函数

◆两个单例的例子(来自博客园:http://www.cnblogs.com/csharp4/archive/2010/10/05/1842915.html)
例一:
public sealed class Singleton
{
   // 在静态私有字段上声明单例
   //定义了一个内联初始化的静态字段(即隐式创建静态构造函数),这样将导致JIT标记为BeforeFieldInit。
   private static readonly Singleton instance = new Singleton();
   
   // 私有构造函数,确保用户在外部不能实例化新的实例
   private Singleton(){}

   // 只读属性返回静态字段
   public static Singleton Instance
   {
      get
      {
         return instance;
      }
   }
}
标记类为sealed是好的,可以防止被集成,然后在子类实例化,使用在静态私有字段上通过new的形式,来保证在该类第一次被调用的时候创建实例,是不错的方式,但有一点需要注意的是,C#其实并不保证实例创建的时机,因为C#规范只是在IL里标记该静态字段是BeforeFieldInit,也就是说静态字段可能在第一次被使用的时候创建,也可能你没使用了,它也帮你创建了,也就是周期更早,我们不能确定到底是什么创建的实例。


例二:
public class Singleton
{
    // 因为下面声明了静态构造函数,所以在第一次访问该类之前,new Singleton()语句不会执行
    private static readonly Singleton _instance = new Singleton();

    public static Singleton Instance
    {
        get { return _instance; }
    }

    private Singleton()
    {
    }

    // 声明静态构造函数就是为了删除IL里的BeforeFieldInit标记
    // 以区别例一中静态自动在使用之前被初始化
    //显式定义了静态构造函数,这样将使JIT不会添加BeforeFieldInit标记,从而控制准确的执行时间。
    static Singleton()
    {
    }
}
这种方式,其实是很不错的,因为他确实保证了是个延迟初始化的单例(通过加静态构造函数),但是该静态构造函数里没有东西哦,所以能有时候会引起误解,尤其是在codereview或者代码优化的时候,不熟悉的人可能直接帮你删除了这段代码,那就又回到了版本2了哦,所以还是需要注意的,不过如果你在这个时机正好有代码需要执行的话,那也不错。

(静态构造函数只在类第一次使用的时候初始化,不用的话不初始化的,不像有些单例,用不用都先初始化好。)

----------------------------------------------------------------------------------------------------------
◆关于静态构造函数的一点总结(来自:http://baike.baidu.com/view/2634573.htm):
在使用静态构造函数的时候应该注意以下几点:
  1、静态构造函数既没有访问修饰符,也没有参数。
  --因为是.NET调用的,所以像public和private等修饰符就没有意义了。
  2、在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类。
  --也就是说我们无法直接调用静态构造函数,也不可能知道静态构造函数何时会被调用。
  3、一个类只能有一个静态构造函数。
  4、无参数的构造函数可以与静态构造函数共存。
  --尽管参数列表相同,但一个属于类,一个属于实例,所以不会冲突。
  5、最多只运行一次。
  6、静态构造函数不可以被继承。
  7、如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。
----------------------------------------------------------------------------------------------------------
◆这是两个自己运行的实例:
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class S2
    {
        private static readonly S2 _s2 = new S2();

        public static S2 s2
        {
            get
            {
                return _s2;
            }
        }

        static S2()
        {
            Console.WriteLine("class test.");
        }

        private S2() { }

    }

    public sealed class S3
    {
        private static readonly S3 _s3 = new S3();

        public static S3 s3
        {
            get
            {
                return _s3;
            }
        }

        private S3() { }

    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(S2.s2);
            Console.WriteLine("----------------------------");
            Console.WriteLine(S3.s3);
            Console.ReadKey();
        }
    }
}
运行结果:


总结:因为静态字段在IL里标记为BeforeFieldInit,所以它可能在第一次被使用之前就已经初始化了(也就是说我们无法确定它初始化的时间);静态构造函数能够确保在使用类之前不会初始化静态字段,而只有在第一次访问类时才进行初始化。
-------------------------------------------------------------------------------------------------------------------------------------------------
补充:还有一种不用static readonly声明和静态构造函数的单例:
例三:用静态私有字段声明单例
public sealed class Singleton
{
   private static Singleton instance;
  
   private Singleton() {}
  
   public static Singleton Instance
   {
      get
      {
         if (instance == null)
         {
            instance = new Singleton();
         }
         return instance;
      }
   }
}
这个版本的主要问题,就是线程安全的问题,当2个请求同时方式这个类的实例的时候,可以会在同一时间点上都创建一个实例,虽然一般不会出异常错误,但是起码不是我们谈论的只保证一个实例了。

posted @   skybirdzw  阅读(672)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示