如何验证属性Setter是否被调用?需编译阶段触发校验的实现方案
如何在C#中校验属性Setter是否被调用(未调用时触发警告/错误)
方案1:构造器强制赋值(从根源避免未调用)
直接在类的构造器中要求传入属性值,确保实例创建时就完成赋值,彻底杜绝Setter没被调用的情况:
public class MyClass { private string _propValue; // 构造器强制传入属性值,创建实例时必须提供 public MyClass(string someProperty) { ArgumentNullException.ThrowIfNull(someProperty); _propValue = someProperty; } public string SomeProperty { get => _propValue; set { ArgumentNullException.ThrowIfNull(value); _propValue = value; } } }
这样创建MyClass实例时必须传参,否则编译报错,直接避免了属性未赋值的问题。
方案2:使用Nullable引用类型(编译时警告)
如果你的项目是C# 8及以上版本,开启Nullable引用类型(在.csproj中设置<Nullable>enable</Nullable>),通过类型系统强制属性必须被赋值:
方式A:Init-only属性(仅初始化时可赋值)
把Setter改成init,确保属性只能在对象初始化阶段赋值,否则编译器会抛出警告:
public class MyClass { private string _propValue; public string SomeProperty { get => _propValue; // 仅允许在对象初始化时赋值 init { ArgumentNullException.ThrowIfNull(value); _propValue = value; } } }
使用时:
// 编译警告:未初始化非可空属性'SomeProperty' var obj = new MyClass(); // 正确写法,无警告 var obj = new MyClass { SomeProperty = "test" };
方式B:构造器初始化校验
保留普通Setter,但确保非可空字段在构造器中初始化,否则编译器会警告字段未初始化:
public class MyClass { // 非可空字段,必须在构造器或初始化器中赋值 private string _propValue; public string SomeProperty { get => _propValue; set { ArgumentNullException.ThrowIfNull(value); _propValue = value; } } public MyClass() { // 如果这里不赋值,编译器会警告:非可空字段未初始化 _propValue = string.Empty; } }
方案3:运行时校验(使用属性/执行方法时触发错误)
如果无法在编译阶段强制,可在运行时检查属性是否已赋值,在获取属性或执行核心方法时抛出异常:
public class MyClass { // 用可空类型标记初始未赋值状态 private string? _propValue; public string SomeProperty { get { // 获取值时检查,未赋值则抛出异常 if (_propValue is null) { throw new InvalidOperationException("SomeProperty尚未赋值,请先设置属性值"); } return _propValue; } set { ArgumentNullException.ThrowIfNull(value); _propValue = value; } } // 或者在类的核心业务方法中校验 public void ExecuteBusinessLogic() { if (_propValue is null) { throw new InvalidOperationException("执行逻辑前必须先设置SomeProperty"); } // 业务逻辑代码 } }
方案4:自定义Roslyn分析器(编译时自定义警告/错误)
如果需要团队级别的强制规范,可编写自定义Roslyn分析器,在编译阶段检测是否设置了目标属性,触发自定义警告或错误。
核心思路是扫描代码中的对象创建表达式,检查是否包含目标属性的初始化:
[DiagnosticAnalyzer(LanguageNames.CSharp)] public class RequiredPropertyAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "RequiredProperty001"; private static readonly LocalizableString Title = "必须设置SomeProperty属性"; private static readonly LocalizableString MessageFormat = "创建{0}实例时必须设置SomeProperty属性"; private static readonly LocalizableString Description = "确保类实例创建时完成RequiredProperty的赋值"; private const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(AnalyzeObjectCreation, SyntaxKind.ObjectCreationExpression); } private void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) { var objCreation = (ObjectCreationExpressionSyntax)context.Node; var typeInfo = context.SemanticModel.GetTypeInfo(objCreation.Type).Type; // 替换为你的目标类名 if (typeInfo?.Name != "MyClass") return; var initializer = objCreation.Initializer; if (initializer == null) { var diagnostic = Diagnostic.Create(Rule, objCreation.GetLocation(), typeInfo.Name); context.ReportDiagnostic(diagnostic); return; } var hasRequiredProperty = initializer.Expressions .OfType<AssignmentExpressionSyntax>() .Any(a => a.Left is IdentifierNameSyntax id && id.Identifier.Text == "SomeProperty"); if (!hasRequiredProperty) { var diagnostic = Diagnostic.Create(Rule, initializer.GetLocation(), typeInfo.Name); context.ReportDiagnostic(diagnostic); } } }
将分析器打包成NuGet包后,项目引用即可在编译时触发自定义警告/错误。
内容的提问来源于stack exchange,提问作者Horman Lewis
相关产品推荐
相关产品推荐

