目录

C#-静态类和单例模式

静态类是使用类型静态成员做事,单例模式是使用类的对象做事。

静态类只有静态成员,一般工具类都是静态类;静态类不能被实例化、不能实现接口。

单例可以实现接口、支持继承和多态,可以扩展;单例支持继承但需要谨慎涉及,实际项目中单例类通常使用 sealed 防止继承。

静态类的成员在初次使用时由CLR初始化。

定义对比:

// 静态类
public static class MathHelper
{
    public static double PI => 3.14;
    
    public static double CircleArea(double r) => PI * r * r;
    
}

//使用:直接通过类名调用
var area = MathHelper.CircleArea(5);
// 单例模式
public sealed class Logger
{
    private static readonly Lazy<Logger> _lazy = new(() => new Logger());
    
    public static Logger Instance => _lazy.Value;
    
    // 私有构造函数,防止外部 new
    private Logger()
    {
        
    }
}

// 使用:通过Instance 获取对象再使用
Logger.Instance.Log("Hello");

核心区别一览表

维度 静态类 单例模式
本质 不能实例化的类 只有一个实例的类
关键字 static class 普通 class + 私有构造函数
实例 没有实例 有且仅有一个实例
构造函数 只有静态构造函数 私有实例构造函数
继承 不可继承 可以(但通常sealed)
实现接口 不可以 可以
多态 不支持 支持
作为参数传递 不可以 可以
依赖注入 不友好 天然支持
序列化 不可以 可以
单元测试 难以 Mock 通过接口可 Mock
内存释放 AppDomain 卸载才释放 理论可控实际不建议
线程安全 需自行处理 需自行处理
典型用途 工具方法/扩展方法 全局服务/管理器

关键区别:能否"面向对象"

// 单例可以实现接口 -> 可替换,可测试
public interface ILogger
{
    void Log(string message);
}

public sealed class FileLogger : ILogger
{
    private static readonly Lazy<FileLogger> _lazy = new(() => new FileLogger());
    public static FileLogger Instance => _lazy.Value;
    
    private FileLogger()
    {
        
    }
    
    public void Log(string message) => File.AppendAllText("log.txt", message);
}

// viewModel 依赖接口,而不是具体类
public class OrderViewModel
{
    private readonly ILogger _logger;
    
    // 可以注入真实单例,也可以注入 Mock
    public OrderViewModel(ILogger logger)
    {
        _logger = logger;
    }
}
    
// ===== 静态类无法做到上面这些 =====
public static class StaticLogger
{
    public static void Log(string message) => Console.WriteLine(message);
}

public class OrderViewModel
{
    public void DoSomething()
    {
        // 硬依赖,无法替换、无法 Mock
        StaticLogger.Log("...");
    }
}

初始化时机比对

// 静态类
public static class AppConfig
{
    // 没有显示静态构造函数 -> CLR 可能提前初始化(beforefieldinit)
    public static readonly string Version = LoadVersion();
}

public static class AppConfig2
{
    public static readonly string Version = LoadVersion();
    
    // 有显示静态构造函数 -> 精确在首次访问时初始化
    static AppConfig2()
    {
        
    }
}
// 单例 -> 完全可控

// 饿汉式:程序启动即创建
public sealed class EagerSingleton
{
    public static readonly EagerSingleton Instance = new();
    
    private EagerSingleton()
    {
        
    }
}

// 懒汉式:首次访问时创建(推荐用 Lazy<T>,天然线程安全)
public sealed class LazySingleton
{
    private static readonly Lazy<LazySingleton> _lazy = new(() => new LazySingleton());

    public static LazySingleton Instance => _lazy.Value;
    private LazySingleton() { }
}

WPF/MVVM 中的实际场景

// 适合静态类:纯工具方法,无状态
public static class ValueConverterHelper
{
    public static Visibility BoolToVisibility(bool value)
        => value ? Visibility.Visible : Visibility.Collapsed;
}

public static class RelayCommandFactory
{
    public static RelayCommand Create(Action<object> execute)
        => new RelayCommand(execute);
}
// 适合单例:全局服务,有状态,需要通知
public sealed class NavigationService : INotifyPropertyChanged
{
    private static readonly Lazy<NavigationService> _lazy = new(() => new());
    public static NavigationService Instance => _lazy.Value;
    private NavigationService() { }

    private object _currentView;
    public object CurrentView
    {
        get => _currentView;
        set { _currentView = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

// XAML 中可以绑定:
// <ContentControl Content="{Binding CurrentView, 
//     Source={x:Static local:NavigationService.Instance}}" />

现代推荐:依赖注入取代手写单例

// 在 App.xaml.cs 或启动类中
var services = new ServiceCollection();

services.AddSingleton<ILogger, FileLogger>();       // 单例生命周期
services.AddTransient<OrderViewModel>();             // 每次新建

var provider = services.BuildServiceProvider();
var vm = provider.GetRequiredService<OrderViewModel>();

依赖注入容器管理的"单例"比手写单例更优:

  • 生命周期由容器控制
  • 依赖关系清晰
  • 天然支持替换和测试

总结

┌─────────────────────────────────────────────────────┐
│                                                     │
│  静态类 → "一组函数的命名空间",无对象,无多态       │
│                                                     │
│  单例   → "全局唯一的对象",是对象,有多态           │
│                                                     │
│  选择标准:需不需要"对象的能力"(接口/继承/传递/注入)│
│                                                     │
│    不需要 → 静态类 (Math, Path, Convert ...)       │
│    需要   → 单例   (Logger, DbContext, Service ...)│
│                                                     │
└─────────────────────────────────────────────────────┘