基于模型特性注解实现C#字符串格式化的最优方案咨询
动态调用自定义格式化特性的最优方案
针对你开发.dat文件生成项目的场景,下面提供几种实用的动态调用PaddedLengthAttribute中Format方法的方案,按性能和复杂度排序:
方案一:反射+特性缓存(推荐入门/中小项目)
核心思路是通过反射获取属性上的自定义特性,同时缓存特性信息避免重复反射开销,实现简单且性能足够应对多数场景。
1. 基础定义(接口+特性)
public interface IFormatterAttribute { string Format(string input); } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = true)] public class PaddedLengthAttribute : Attribute, IFormatterAttribute { public int Length { get; } public char PadChar { get; } public PaddedLengthAttribute(int length, char padChar = '0') { Length = length; PadChar = padChar; } public string Format(string input) { // 处理空值,确保输出长度严格符合要求 return input?.PadRight(Length, PadChar)?.Substring(0, Length) ?? new string(PadChar, Length); } }
2. 特性缓存类
public static class FormatterCache { private static readonly Dictionary<Type, Dictionary<PropertyInfo, IFormatterAttribute>> _attributeCache = new(); // 缓存指定类型的属性与对应格式化特性 public static Dictionary<PropertyInfo, IFormatterAttribute> GetTypeFormatters(Type targetType) { if (_attributeCache.TryGetValue(targetType, out var cachedFormatters)) return cachedFormatters; var formatters = targetType.GetProperties() .Where(prop => Attribute.IsDefined(prop, typeof(PaddedLengthAttribute))) .ToDictionary( prop => prop, prop => (IFormatterAttribute)prop.GetCustomAttribute(typeof(PaddedLengthAttribute))! ); _attributeCache[targetType] = formatters; return formatters; } }
3. 动态格式化方法
public static string GenerateDatContent(object model) { if (model == null) throw new ArgumentNullException(nameof(model)); var type = model.GetType(); var formatters = FormatterCache.GetTypeFormatters(type); var contentBuilder = new StringBuilder(); foreach (var (prop, formatter) in formatters) { var rawValue = prop.GetValue(model)?.ToString() ?? string.Empty; contentBuilder.Append(formatter.Format(rawValue)); } return contentBuilder.ToString(); }
使用示例
public class Header { [PaddedLength(7)] public string RECORD_TYPE { get; set; } = string.Empty; [PaddedLength(10, ' ')] public string USER_NAME { get; set; } = string.Empty; } // 生成.dat内容 var header = new Header { RECORD_TYPE = "ABC", USER_NAME = "Alice" }; var datLine = GenerateDatContent(header); // 输出结果:"ABC0000Alice "
方案二:表达式树编译(适合高频调用场景)
如果项目中.dat生成操作非常频繁,纯反射的GetValue会有性能瓶颈,用表达式树编译属性取值委托,能大幅提升运行效率。
修改缓存类(替换反射取值为表达式委托)
public static class FormatterCache { private static readonly Dictionary<Type, List<(Func<object, string> ValueGetter, IFormatterAttribute Formatter)>> _formatterCache = new(); public static List<(Func<object, string>, IFormatterAttribute)> GetTypeFormatters(Type targetType) { if (_formatterCache.TryGetValue(targetType, out var cachedFormatters)) return cachedFormatters; var formatters = new List<(Func<object, string>, IFormatterAttribute)>(); foreach (var prop in targetType.GetProperties().Where(p => Attribute.IsDefined(p, typeof(PaddedLengthAttribute)))) { // 构建表达式树:将对象转换为目标类型,直接访问属性并调用ToString var objParam = Expression.Parameter(typeof(object), "obj"); var castToTarget = Expression.Convert(objParam, targetType); var propAccess = Expression.Property(castToTarget, prop); var toStringCall = Expression.Call(propAccess, typeof(object).GetMethod("ToString", Type.EmptyTypes)!); var castToString = Expression.Convert(toStringCall, typeof(string)); // 编译为委托,避免重复反射 var valueGetter = Expression.Lambda<Func<object, string>>(castToString, objParam).Compile(); var formatter = (IFormatterAttribute)prop.GetCustomAttribute(typeof(PaddedLengthAttribute))!; formatters.Add((valueGetter, formatter)); } _formatterCache[targetType] = formatters; return formatters; } }
格式化方法复用方案一的逻辑,仅取值方式变更
public static string GenerateDatContent(object model) { if (model == null) throw new ArgumentNullException(nameof(model)); var type = model.GetType(); var formatters = FormatterCache.GetTypeFormatters(type); var contentBuilder = new StringBuilder(); foreach (var (getter, formatter) in formatters) { var rawValue = getter(model) ?? string.Empty; contentBuilder.Append(formatter.Format(rawValue)); } return contentBuilder.ToString(); }
方案三:源生成器(性能最优,适合大规模/高性能项目)
使用C#源生成器在编译时直接生成格式化代码,彻底避免运行时反射,性能拉满,适合对性能要求极高的场景。
1. 创建源生成器项目
[Generator] public class DatFormatterGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { } public void Execute(GeneratorExecutionContext context) { // 遍历所有带有PaddedLength特性属性的类 foreach (var classSyntax in context.Compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes()) .OfType<ClassDeclarationSyntax>() .Where(cls => cls.Members.OfType<PropertyDeclarationSyntax>() .Any(p => p.AttributeLists.SelectMany(al => al.Attributes) .Any(a => a.Name.ToString() == "PaddedLength")))) { var classSymbol = context.Compilation.GetSemanticModel(classSyntax.SyntaxTree) .GetDeclaredSymbol(classSyntax) as INamedTypeSymbol; if (classSymbol == null) continue; // 获取类中带PaddedLength特性的属性 var targetProperties = classSymbol.GetMembers() .OfType<IPropertySymbol>() .Where(p => p.GetAttributes().Any(a => a.AttributeClass?.Name == "PaddedLength")); // 生成格式化类代码 var sourceBuilder = new StringBuilder(); sourceBuilder.AppendLine($"public static class {classSymbol.Name}DatFormatter"); sourceBuilder.AppendLine("{"); sourceBuilder.AppendLine($" public static string Format({classSymbol.Name} model)"); sourceBuilder.AppendLine(" {"); sourceBuilder.AppendLine(" if (model == null) throw new System.ArgumentNullException(nameof(model));"); sourceBuilder.AppendLine(" var sb = new System.Text.StringBuilder();"); foreach (var prop in targetProperties) { var attr = prop.GetAttributes().First(a => a.AttributeClass?.Name == "PaddedLength"); var length = attr.ConstructorArguments[0].Value as int? ?? 0; var padChar = attr.ConstructorArguments.Count > 1 ? attr.ConstructorArguments[1].Value as char? : '0'; sourceBuilder.AppendLine($" sb.Append((model.{prop.Name}?.ToString() ?? string.Empty).PadRight({length}, '{padChar}').Substring(0, {length}));"); } sourceBuilder.AppendLine(" return sb.ToString();"); sourceBuilder.AppendLine(" }"); sourceBuilder.AppendLine("}"); // 添加生成的代码到编译过程 context.AddSource($"{classSymbol.Name}DatFormatter.g.cs", sourceBuilder.ToString()); } } }
2. 主项目中使用
编译后会自动生成对应类的格式化工具类,直接调用即可:
var header = new Header { RECORD_TYPE = "ABC", USER_NAME = "Alice" }; var datLine = HeaderDatFormatter.Format(header);
方案选型建议
- 反射+缓存:实现成本最低,性能满足90%以上的业务场景,优先选这个。
- 表达式树:比纯反射快2-3倍,适合高频调用的场景,实现复杂度中等。
- 源生成器:编译时生成代码,运行时零开销,适合性能敏感或大规模项目,实现复杂度稍高。
内容的提问来源于stack exchange,提问作者Unknown Coder
相关产品推荐
相关产品推荐

