You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于模型特性注解实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:40:56