当向类中添加新属性时,如何强制更改某些方法
本文关键字:何强制 方法 添加 新属性 属性 | 更新日期: 2023-09-27 17:57:32
我有以下问题
这是我们使用的第三方类(所以我不能更改它)
public class ThirdPartyEmployee
{
public string F_Name { get; set; }
public string L_Name { get; set; }
public DateTime Date_of_birth { get; set; }
public string Telephone1 { get; set; }
public string Telephone2 { get; set; }
public string Position { get; set; }
//..... and so on
}
此外,我们还有自己的更小更好的员工级
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobileTelephone { get; set; }
}
有时我们需要将第三方阶级转变为我们自己的阶级。有扩展方法
public static class ThirdPartyExtensions
{
public static Employee ConvertTo(this ThirdPartyEmployee thirdPartyEmployee)
{
var result = new Employee();
result.FirstName = thirdPartyEmployee.F_Name;
result.LastName = thirdPartyEmployee.L_Name;
result.MobileTelephone = thirdPartyEmployee.Telephone1;
return result;
}
}
现在谈谈这个问题如果有人考虑向Employee类添加一些其他属性,他/她可能会忘记更改ConvertTo方法。我们怎样才能避免它理想情况下,我希望出现一些编译错误
有什么建议吗?
如果Employee
类只是一个容器,那么有一种简单的方法:
public class Employee
{
private readonly string firstName;
public Employee(string firstName)
{
this.firstName = firstName;
}
}
现在,您的转换方法别无选择,只能传递所有参数,因此在转换方法未更新时会出现编译器错误。
当然,这仍然不是万无一失的——如果你也关心改变论点,这也没有多大帮助。
现在我们有了Roslyn,在Visual Studio中进行了很好的集成,您实际上可以使用Roslyn分析器来制造自己的编译器错误。如果你不怕弄脏自己的手,这将是一个很好的机会来展示这样的东西有多有用。遗憾的是,它现在不太容易使用,需要"正确的思维"才能很好地使用。它将允许您制定规则,例如"类转换扩展方法必须分配结果类中的所有属性"。
不能用标准方法创建编译错误。可能有一些VisualStudio插件允许您这样做。
但这可能不是必要的:可以更改CopyTo
方法,以便它不硬编码所有要复制的属性,而是使用反射来获得所有要复制公共属性的列表。
开始的示例代码:
FieldInfo[] myObjectFields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo fi in myObjectFields)
{
i.SetValue(destination, fi.GetValue(source));
}
处理不同的属性名称:您可以引入一个属性,该属性允许您指定ThirdPartyEmployee
的哪个属性转换为Employee
的哪个属性。这也可以使用反射进行评估。
示例:
public class Employee
{
[CopyFromThirdPartyEmployee("F_Name")]
public string FirstName { get; set; }
[CopyFromThirdPartyEmployee("L_Name")]
public string LastName { get; set; }
[CopyFromThirdPartyEmployee("Telephone1")]
public string MobileTelephone { get; set; }
}
当CopyTo
方法发现一个没有所需映射属性的公共属性时,可以让它抛出异常。通过这种方式,您可以确保每个属性都有该属性,但这将是运行时错误,而不是编译时错误。
另一种方法是简单地将Employee
作为ThirdPartyEmployee
:的包装器
public class Employee
{
private ThirdPartyEmployee _baseEmployee;
public Employee() { _baseEmployee = new ThirdPartyEmployee(); }
public Employee(ThirdPartyEmployee e) { _baseEmployee = e; }
public string FirstName
{
get { return _baseEmployee.F_Name; }
set { _baseEmployee.F_Name = value; }
}
...
}
这样你就会注意到,如果你不能访问一个属性,你就没有实现它。缺点是,每个员工都会基于ThirdPartyEmployee
。
您可以在反射的帮助下完成此操作,但需要用于名称映射的字典:
public static class ThirdPartyExtensions
{
static Dictionary<string, string> map;
static ThirdPartyExtensions()
{
map = new Dictionary<string, string>{ {"F_Name", "FirstName"} /*and others*/};
}
public static Employee ConvertTo(this ThirdPartyEmployee thirdPartyEmployee)
{
var result = new Employee();
if(map.Count < typeof(Employee).GetProperties().Count())
throw new Exception("Forget to add mapping for new field!");
foreach(var prop in typeof(ThirdPartyEmployee).GetProperties())
if(map.ContainsKey(prop.Name))
{
var temp = typeof(Employee).GetProperty(map[prop.Name]);
temp.SetValue(result, prop.GetValue(thirdPartyEmployee));
}
return result;
}
}
使用Roslyn分析器可以产生超出编译器范围的编译时(和IntelliSense)错误。下面是一个非常简单的分析器实现,用于检查方法返回的类型的所有属性是否已分配。它没有考虑控制流(例如if
)。
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AssignAllPropertiesAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor("AssignAllPropertiesAnalyzer",
"All properties must be assigned.", "All properties of the return type must be assigned.", "Correctness",
DiagnosticSeverity.Warning, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration);
}
private static void AnalyzeMethod(SyntaxNodeAnalysisContext context)
{
var methodNode = (MethodDeclarationSyntax)context.Node;
var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodNode);
if (methodSymbol.GetReturnTypeAttributes().Any(x => x.AttributeClass.Name == "AssignAllPropertiesAttribute"))
{
var properties = methodSymbol.ReturnType.GetMembers().OfType<IPropertySymbol>().Where(x => !x.IsReadOnly).ToList();
foreach (var assignmentNode in methodNode.DescendantNodes().OfType<AssignmentExpressionSyntax>())
{
var propertySymbol = context.SemanticModel.GetSymbolInfo(assignmentNode.Left).Symbol as IPropertySymbol;
if (propertySymbol != null)
{
properties.Remove(propertySymbol);
}
}
if (properties.Count > 0)
{
var diagnostic = Diagnostic.Create(Rule, methodSymbol.Locations[0]);
context.ReportDiagnostic(diagnostic);
}
}
}
分析器假定一个名为AssignAllProperties
的属性被应用于方法的返回类型。在以下示例中,~~~~~~
标记分析仪将产生诊断的位置。
class A
{
public string S { get; set; }
}
[return: AssignAllProperties]
public static A Create()
~~~~~~
{
return new A();
}
分析器既可以作为VSIX安装,也可以作为NuGet包安装。我建议始终使用NuGet方法——它会将分析器应用于使用代码的每个人,并允许您更改严重性(例如错误),从而导致编译失败。要开始构建分析器库,请安装Roslyn SDK并创建一个带有代码修复的analyzerC#项目。
您不能为此生成编译错误,但是。。。我会将转换方法移到Employee
类。我建议避免依赖数据的扩展方法(类似于其他类的属性)
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobileTelephone { get; set; }
public static Employee From(ThirdPartyEmployee employee)
{
var result = new Employee();
result.FirstName = thirdPartyEmployee.F_Name;
result.LastName = thirdPartyEmployee.L_Name;
result.MobileTelephone = thirdPartyEmployee.Telephone1;
return result;
}
}
通过这种方式,您可以将所有功能保留在正确的类/文件中,其他人可以清楚地看到他们是否添加了属性。