C#非静态构造函数赋值静态字段生效原因及JUST.net静态方法日志方案
问题1:实例化类后静态方法能获取logger的原因
你代码里的_logger是静态字段,静态字段属于类本身而非类的实例,整个应用生命周期内仅会保留一份值,所有实例和静态方法共享该字段。
你在类的实例构造函数中实现了对静态字段_logger的赋值,所以只要你执行过一次new Transformations(logger)的实例化操作,静态字段_logger就会被赋值为传入的logger实例,后续调用静态方法MapAddress时自然能读取到已赋值的_logger。如果从未实例化过该类,静态字段_logger会保持默认值null,调用日志方法时就会触发空引用异常。
注意:当前实现存在风险:多次实例化类会覆盖原有静态logger的值,多线程场景下同时实例化也可能出现线程安全问题。
问题2:更优的实现方案
因为业务限制只能静态调用方法,我们可以通过显式静态初始化的方式优化实现,避免依赖类的实例化操作,同时保证线程安全:
优化后的代码实现
public class Transformations { // 加volatile保证多线程场景下的读取可见性 private static volatile ILogger<Transformations> _logger; // 锁对象用于线程安全的初始化控制 private static readonly object _lockObj = new(); // 私有构造函数,禁止外部随意实例化该类 private Transformations() {} // 显式静态初始化方法,应用启动/测试初始化时调用一次即可 public static void Init(ILogger<Transformations> logger) { if (_logger != null) return; lock (_lockObj) { if (_logger != null) return; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } } public static string MapAddress(string? streetNumber, string? streetName) { // 增加初始化校验,抛出友好的错误提示 if (_logger == null) { throw new InvalidOperationException("Transformations类未初始化,请先调用Init方法注入日志实例"); } if(string.IsNullOrEmpty(streetNumber)) { _logger.LogError("The property was not provided."); } // 其他业务逻辑 } }
配套的测试代码调整
public class AddressUnitTest { // 测试类静态构造函数,整个测试生命周期仅执行一次初始化 static AddressUnitTest() { var serilog = new LoggerConfiguration().WriteTo.Console().CreateLogger(); var loggerFactory = new LoggerFactory().AddSerilog(serilog); var logger = loggerFactory.CreateLogger<Transformations>(); // 调用静态初始化方法 Transformations.Init(logger); } [Fact] public void Address(string? streetNumber, string? streetName) { var actualValue = Transformations.MapAddress(streetNumber, streetName); // 其他断言逻辑 } }
方案优势
- 不再依赖类的实例化操作,符合你只能静态调用的业务限制
- 双重检查锁保证了多线程场景下初始化的线程安全,避免logger被重复赋值
- 显式的初始化方法和空校验,出现未初始化的问题时会抛出友好提示,便于排查
- 私有构造函数避免了外部随意实例化类导致的logger被意外覆盖的问题
内容的提问来源于stack exchange,提问作者Trevor.Screws
相关产品推荐
相关产品推荐

