Skip to content

ToString 鐢熸垚鍣ㄧず渚?

椤圭洰浣嶇疆

04-ToStringGenerator/

瀛︿範鐩爣

  • 浣跨敤鐗规€ф爣璁扮被
  • 鍒嗘瀽绫荤殑灞炴€у拰瀛楁
  • 鐢熸垚鏂规硶浠g爜
  • 澶勭悊 partial 绫?
  • 瀹炵幇鑷畾涔夋牸寮忓寲
  • 澶勭悊澶嶆潅绫诲瀷鍜屽祵濂楀璞?
  • 鏀寔閰嶇疆閫夐」

鍔熻兘璇存槑

鑷姩涓烘爣璁颁簡 [GenerateToString] 鐗规€х殑绫荤敓鎴?ToString() 鏂规硶锛屾敮鎸侊細

  • 鉁?鑷姩鍖呭惈鎵€鏈夊叕鍏卞睘鎬?
  • 鉁?鑷畾涔夋牸寮忓寲閫夐」
  • 鉁?蹇界暐鐗瑰畾灞炴€?
  • 鉁?澶勭悊宓屽瀵硅薄
  • 鉁?鏀寔闆嗗悎绫诲瀷
  • 鉁?鎬ц兘浼樺寲锛堜娇鐢?StringBuilder锛?

宸ヤ綔娴佺▼


蹇€熷紑濮?

瀹氫箟鐗规€?

csharp
[AttributeUsage(AttributeTargets.Class)]
public class GenerateToStringAttribute : Attribute
{
}

鏍囪绫?

csharp
[GenerateToString]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

鐢熸垚鐨勪唬鐮?

csharp
partial class Person
{
    public override string ToString()
    {
        return $"Person {{ Name = {Name}, Age = {Age}, Email = {Email} }}";
    }
}

浣跨敤

csharp
var person = new Person 
{ 
    Name = "寮犱笁", 
    Age = 30, 
    Email = "zhangsan@example.com" 
};

Console.WriteLine(person);
// 杈撳嚭: Person { Name = 寮犱笁, Age = 30, Email = zhangsan@example.com }

鐢熸垚鍣ㄥ疄鐜?

csharp
[Generator]
public class ToStringGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 1. 鏌ユ壘甯︽湁 GenerateToString 鐗规€х殑绫?
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => 
                    node is ClassDeclarationSyntax cls &&
                    cls.AttributeLists.Count > 0,
                transform: (ctx, _) => GetClassInfo(ctx)
            )
            .Where(info => info != null);

        // 2. 鐢熸垚 ToString 鏂规硶
        context.RegisterSourceOutput(
            classDeclarations,
            (spc, classInfo) =>
            {
                var source = GenerateToString(classInfo);
                spc.AddSource($"{classInfo.ClassName}.g.cs", source);
            });
    }

    private ClassInfo? GetClassInfo(GeneratorSyntaxContext context)
    {
        var classDecl = (ClassDeclarationSyntax)context.Node;
        var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl) 
            as INamedTypeSymbol;

        if (symbol == null) return null;

        // 妫€鏌ユ槸鍚︽湁 GenerateToString 鐗规€?
        var hasAttribute = symbol.GetAttributes()
            .Any(a => a.AttributeClass?.Name == "GenerateToStringAttribute");

        if (!hasAttribute) return null;

        // 鑾峰彇鎵€鏈夊叕鍏卞睘鎬?
        var properties = symbol.GetMembers()
            .OfType<IPropertySymbol>()
            .Where(p => p.DeclaredAccessibility == Accessibility.Public)
            .Select(p => p.Name)
            .ToList();

        return new ClassInfo(
            symbol.Name,
            symbol.ContainingNamespace.ToDisplayString(),
            properties
        );
    }

    private string GenerateToString(ClassInfo classInfo)
    {
        var propertyStrings = classInfo.Properties
            .Select(p => $"{p} = {{{p}}}");
        
        var toStringBody = string.Join(", ", propertyStrings);

        return $@"
namespace {classInfo.Namespace}
{{
    partial class {classInfo.ClassName}
    {{
        public override string ToString()
        {{
            return $""{classInfo.ClassName} {{ {toStringBody} }}"";
        }}
    }}
}}";
    }

    private record ClassInfo(
        string ClassName,
        string Namespace,
        List<string> Properties
    );
}

鍏抽敭鎶€鏈偣

1. Partial 绫?

鐢熸垚鍣ㄧ敓鎴愮殑浠g爜蹇呴』浣跨敤 partial 鍏抽敭瀛楋細

csharp
// 鐢ㄦ埛浠g爜
[GenerateToString]
public partial class Person { }

// 鐢熸垚鐨勪唬鐮?
partial class Person
{
    public override string ToString() { }
}

2. 鑾峰彇灞炴€т俊鎭?

csharp
var properties = typeSymbol.GetMembers()
    .OfType<IPropertySymbol>()
    .Where(p => p.DeclaredAccessibility == Accessibility.Public);

3. 瀛楃涓叉彃鍊?

鐢熸垚鐨?ToString 浣跨敤瀛楃涓叉彃鍊硷細

csharp
return $"Person {{ Name = {Name}, Age = {Age} }}";

澧炲己鍔熻兘

鏀寔鑷畾涔夋牸寮?

csharp
[GenerateToString(Format = "Name: {Name}, Age: {Age}")]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

鏀寔蹇界暐灞炴€?

csharp
public partial class Person
{
    public string Name { get; set; }
    
    [ToStringIgnore]
    public string Password { get; set; }  // 涓嶅寘鍚湪 ToString 涓?
}

鏀寔宓屽瀵硅薄

csharp
public partial class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }  // 鑷姩璋冪敤 Address.ToString()
}

杩愯绀轰緥

bash
cd 04-ToStringGenerator
dotnet build
dotnet run --project ToStringGenerator.Consumer

甯歌闂

Q: 涓轰粈涔堝繀椤讳娇鐢?partial 绫伙紵

A: 鍥犱负鐢熸垚鍣ㄧ敓鎴愮殑浠g爜鍜岀敤鎴蜂唬鐮侀渶瑕佸悎骞舵垚涓€涓被銆?

Q: 濡備綍澶勭悊缁ф壙鐨勫睘鎬э紵

A: 浣跨敤 symbol.GetMembers() 鍙幏鍙栧綋鍓嶇被鐨勬垚鍛樸€傚鏋滈渶瑕佸寘鍚熀绫诲睘鎬э紝浣跨敤锛?

csharp
var allProperties = typeSymbol.GetMembers()
    .Concat(typeSymbol.BaseType?.GetMembers() ?? Enumerable.Empty<ISymbol>())
    .OfType<IPropertySymbol>();

涓嬩竴姝?

  • [Builder 鐢熸垚鍣ㄧず渚媇(/examples/) - 瀛︿範鏇村鏉傜殑浠g爜鐢熸垚
  • 鏌ョ湅浠g爜鐢熸垚 API
  • [鏌ョ湅鏈€浣冲疄璺礭(/api/best-practices)

瀹屾暣椤圭洰缁撴瀯

鐩綍缁撴瀯

04-ToStringGenerator/
鈹溾攢鈹€ ToStringGenerator.slnx
鈹溾攢鈹€ ToStringGenerator.Generator/
鈹?  鈹溾攢鈹€ ToStringGenerator.Generator.csproj
鈹?  鈹溾攢鈹€ ToStringGenerator.cs
鈹?  鈹溾攢鈹€ Attributes/
鈹?  鈹?  鈹溾攢鈹€ GenerateToStringAttribute.cs
鈹?  鈹?  鈹斺攢鈹€ ToStringIgnoreAttribute.cs
鈹?  鈹斺攢鈹€ Models/
鈹?      鈹斺攢鈹€ ClassInfo.cs
鈹斺攢鈹€ ToStringGenerator.Consumer/
    鈹溾攢鈹€ ToStringGenerator.Consumer.csproj
    鈹溾攢鈹€ Program.cs
    鈹斺攢鈹€ Models/
        鈹溾攢鈹€ Person.cs
        鈹溾攢鈹€ Product.cs
        鈹斺攢鈹€ Order.cs

椤圭洰閰嶇疆

ToStringGenerator.Generator.csproj:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
  </ItemGroup>

</Project>

ToStringGenerator.Consumer.csproj:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\ToStringGenerator.Generator\ToStringGenerator.Generator.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false" />
  </ItemGroup>

</Project>

鐢熸垚娴佺▼鍥?


鐢熸垚鍣ㄥ疄鐜拌瑙?

瀹屾暣鐢熸垚鍣ㄤ唬鐮?

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
using System.Text;

namespace ToStringGenerator.Generator
{
    /// <summary>
    /// 涓哄甫鏈?[GenerateToString] 鐗规€х殑绫荤敓鎴?ToString 鏂规硶
    /// </summary>
    [Generator]
    public class ToStringGenerator : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            // 1. 鏌ユ壘甯︽湁 GenerateToString 鐗规€х殑绫?
            var classDeclarations = context.SyntaxProvider
                .CreateSyntaxProvider(
                    // 蹇€熻娉曡繃婊?
                    predicate: static (node, _) => IsCandidateClass(node),
                    
                    // 璇箟鍒嗘瀽鍜屾暟鎹彁鍙?
                    transform: static (ctx, _) => GetClassInfo(ctx)
                )
                .Where(static info => info != null);

            // 2. 鐢熸垚 ToString 鏂规硶
            context.RegisterSourceOutput(
                classDeclarations,
                static (spc, classInfo) => GenerateToStringMethod(spc, classInfo!)
            );
        }

        /// <summary>
        /// 蹇€熸鏌ワ細鏄惁鏄€欓€夌被
        /// </summary>
        private static bool IsCandidateClass(SyntaxNode node)
        {
            // 蹇呴』鏄被澹版槑
            if (node is not ClassDeclarationSyntax classDecl)
                return false;

            // 蹇呴』鏈夌壒鎬?
            if (classDecl.AttributeLists.Count == 0)
                return false;

            // 蹇呴』鏄?partial 绫?
            if (!classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
                return false;

            return true;
        }

        /// <summary>
        /// 鎻愬彇绫讳俊鎭?
        /// </summary>
        private static ClassInfo? GetClassInfo(GeneratorSyntaxContext context)
        {
            var classDecl = (ClassDeclarationSyntax)context.Node;
            var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl);

            if (symbol == null)
                return null;

            // 妫€鏌ユ槸鍚︽湁 GenerateToString 鐗规€?
            var attribute = symbol.GetAttributes()
                .FirstOrDefault(a => a.AttributeClass?.Name == "GenerateToStringAttribute");

            if (attribute == null)
                return null;

            // 鑾峰彇鐗规€у弬鏁?
            var includeFields = GetAttributeValue<bool>(attribute, "IncludeFields", false);
            var includePrivate = GetAttributeValue<bool>(attribute, "IncludePrivate", false);

            // 鑾峰彇灞炴€т俊鎭?
            var properties = symbol.GetMembers()
                .OfType<IPropertySymbol>()
                .Where(p => ShouldIncludeProperty(p, includePrivate))
                .Select(p => new PropertyInfo(
                    Name: p.Name,
                    Type: p.Type.ToDisplayString(),
                    IsIgnored: HasIgnoreAttribute(p)
                ))
                .Where(p => !p.IsIgnored)
                .ToImmutableArray();

            // 鑾峰彇瀛楁淇℃伅锛堝鏋滈渶瑕侊級
            var fields = includeFields
                ? symbol.GetMembers()
                    .OfType<IFieldSymbol>()
                    .Where(f => !f.IsStatic && !f.IsConst)
                    .Where(f => ShouldIncludeField(f, includePrivate))
                    .Select(f => new PropertyInfo(
                        Name: f.Name,
                        Type: f.Type.ToDisplayString(),
                        IsIgnored: HasIgnoreAttribute(f)
                    ))
                    .Where(f => !f.IsIgnored)
                    .ToImmutableArray()
                : ImmutableArray<PropertyInfo>.Empty;

            return new ClassInfo(
                Name: symbol.Name,
                Namespace: symbol.ContainingNamespace.ToDisplayString(),
                Properties: properties,
                Fields: fields
            );
        }

        /// <summary>
        /// 鐢熸垚 ToString 鏂规硶浠g爜
        /// </summary>
        private static void GenerateToStringMethod(
            SourceProductionContext context,
            ClassInfo classInfo)
        {
            var sb = new StringBuilder();

            // 鏂囦欢澶?
            sb.AppendLine("// <auto-generated/>");
            sb.AppendLine("#nullable enable");
            sb.AppendLine();

            // 鍛藉悕绌洪棿
            sb.AppendLine($"namespace {classInfo.Namespace}");
            sb.AppendLine("{");

            // 绫诲0鏄?
            sb.AppendLine($"    partial class {classInfo.Name}");
            sb.AppendLine("    {");

            // ToString 鏂规硶
            sb.AppendLine("        public override string ToString()");
            sb.AppendLine("        {");

            // 浣跨敤 StringBuilder 鏋勫缓瀛楃涓诧紙鎬ц兘浼樺寲锛?
            sb.AppendLine("            var sb = new System.Text.StringBuilder();");
            sb.AppendLine($"            sb.Append(\"{classInfo.Name} {{ \");");

            // 娣诲姞灞炴€?
            var allMembers = classInfo.Properties.Concat(classInfo.Fields).ToList();
            for (int i = 0; i < allMembers.Count; i++)
            {
                var member = allMembers[i];
                var isLast = i == allMembers.Count - 1;

                sb.AppendLine($"            sb.Append(\"{member.Name} = \");");
                sb.AppendLine($"            sb.Append({member.Name});");

                if (!isLast)
                {
                    sb.AppendLine("            sb.Append(\", \");");
                }
            }

            sb.AppendLine("            sb.Append(\" }\");");
            sb.AppendLine("            return sb.ToString();");

            // 鍏抽棴鏂规硶鍜岀被
            sb.AppendLine("        }");
            sb.AppendLine("    }");
            sb.AppendLine("}");

            // 娣诲姞婧愭枃浠?
            context.AddSource(
                $"{classInfo.Name}.ToString.g.cs",
                sb.ToString()
            );
        }

        // 杈呭姪鏂规硶
        private static bool ShouldIncludeProperty(IPropertySymbol property, bool includePrivate)
        {
            if (includePrivate)
                return true;

            return property.DeclaredAccessibility == Accessibility.Public;
        }

        private static bool ShouldIncludeField(IFieldSymbol field, bool includePrivate)
        {
            if (includePrivate)
                return true;

            return field.DeclaredAccessibility == Accessibility.Public;
        }

        private static bool HasIgnoreAttribute(ISymbol symbol)
        {
            return symbol.GetAttributes()
                .Any(a => a.AttributeClass?.Name == "ToStringIgnoreAttribute");
        }

        private static T GetAttributeValue<T>(AttributeData attribute, string name, T defaultValue)
        {
            var namedArg = attribute.NamedArguments
                .FirstOrDefault(arg => arg.Key == name);

            if (namedArg.Value.Value is T value)
                return value;

            return defaultValue;
        }
    }

    /// <summary>
    /// 绫讳俊鎭紙鐢ㄤ簬缂撳瓨锛?
    /// </summary>
    internal record ClassInfo(
        string Name,
        string Namespace,
        ImmutableArray<PropertyInfo> Properties,
        ImmutableArray<PropertyInfo> Fields
    );

    /// <summary>
    /// 灞炴€?瀛楁淇℃伅
    /// </summary>
    internal record PropertyInfo(
        string Name,
        string Type,
        bool IsIgnored
    );
}

浠g爜鍒嗘瀽

1. 涓ら樁娈佃繃婊?

csharp
// 闃舵 1锛氬揩閫熻娉曡繃婊わ紙涓嶄娇鐢ㄨ涔夋ā鍨嬶級
predicate: static (node, _) => IsCandidateClass(node)

// 闃舵 2锛氳涔夊垎鏋愶紙浣跨敤璇箟妯″瀷锛?
transform: static (ctx, _) => GetClassInfo(ctx)

浼樺娍锛?

  • 绗竴闃舵闈炲父蹇紝杩囨护鎺夊ぇ閮ㄥ垎涓嶇浉鍏崇殑鑺傜偣
  • 绗簩闃舵鍙鐞嗗€欓€夎妭鐐癸紝鍑忓皯璇箟鍒嗘瀽寮€閿€

2. 鏁版嵁妯″瀷璁捐

csharp
// 浣跨敤 record 绫诲瀷锛岃嚜鍔ㄥ疄鐜?IEquatable
internal record ClassInfo(
    string Name,
    string Namespace,
    ImmutableArray<PropertyInfo> Properties,
    ImmutableArray<PropertyInfo> Fields
);

浼樺娍锛?

  • 鑷姩瀹炵幇鍊肩浉绛夋瘮杈?
  • 鏀寔澧為噺鐢熸垚鍣ㄧ紦瀛?
  • 涓嶅彲鍙橈紝绾跨▼瀹夊叏

3. 鎬ц兘浼樺寲

csharp
// 浣跨敤 StringBuilder 鑰屼笉鏄瓧绗︿覆鎷兼帴
sb.AppendLine("var sb = new System.Text.StringBuilder();");
sb.AppendLine($"sb.Append(\"{classInfo.Name} {{ \");");

浼樺娍锛?

  • 閬垮厤澶ч噺瀛楃涓插垎閰?
  • 鎻愰珮杩愯鏃舵€ц兘

鐗规€ц璁?

GenerateToString 鐗规€?

csharp
namespace ToStringGenerator
{
    /// <summary>
    /// 鏍囪绫讳互鑷姩鐢熸垚 ToString 鏂规硶
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    public sealed class GenerateToStringAttribute : Attribute
    {
        /// <summary>
        /// 鏄惁鍖呭惈瀛楁锛堥粯璁ゅ彧鍖呭惈灞炴€э級
        /// </summary>
        public bool IncludeFields { get; set; }

        /// <summary>
        /// 鏄惁鍖呭惈绉佹湁鎴愬憳锛堥粯璁ゅ彧鍖呭惈鍏叡鎴愬憳锛?
        /// </summary>
        public bool IncludePrivate { get; set; }

        /// <summary>
        /// 鑷畾涔夋牸寮忥紙鍙€夛級
        /// </summary>
        public string? Format { get; set; }
    }
}

ToStringIgnore 鐗规€?

csharp
namespace ToStringGenerator
{
    /// <summary>
    /// 鏍囪灞炴€ф垨瀛楁浠ュ湪 ToString 涓拷鐣?
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, 
        Inherited = false, AllowMultiple = false)]
    public sealed class ToStringIgnoreAttribute : Attribute
    {
    }
}

浣跨敤绀轰緥

csharp
// 绀轰緥 1锛氬熀鏈敤娉?
[GenerateToString]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// 绀轰緥 2锛氬寘鍚瓧娈?
[GenerateToString(IncludeFields = true)]
public partial class Product
{
    public string Name { get; set; }
    private decimal price;  // 灏嗚鍖呭惈
}

// 绀轰緥 3锛氬拷鐣ョ壒瀹氬睘鎬?
[GenerateToString]
public partial class User
{
    public string Username { get; set; }
    
    [ToStringIgnore]
    public string Password { get; set; }  // 涓嶄細鍖呭惈鍦?ToString 涓?
}

// 绀轰緥 4锛氬寘鍚鏈夋垚鍛?
[GenerateToString(IncludePrivate = true)]
public partial class Account
{
    public string Id { get; set; }
    private string secretKey;  // 灏嗚鍖呭惈
}

浠g爜鐢熸垚鎶€宸?

鎶€宸?1锛氫娇鐢?StringBuilder

csharp
// 鉂?閬垮厤锛氬瓧绗︿覆鎷兼帴
public override string ToString()
{
    string result = "Person { ";
    result += "Name = " + Name + ", ";
    result += "Age = " + Age;
    result += " }";
    return result;
}

// 鉁?鎺ㄨ崘锛氫娇鐢?StringBuilder
public override string ToString()
{
    var sb = new StringBuilder();
    sb.Append("Person { ");
    sb.Append("Name = ");
    sb.Append(Name);
    sb.Append(", Age = ");
    sb.Append(Age);
    sb.Append(" }");
    return sb.ToString();
}

鎶€宸?2锛氬鐞?null 鍊?

csharp
// 鐢熸垚鐨勪唬鐮佸簲璇ュ鐞?null
public override string ToString()
{
    var sb = new StringBuilder();
    sb.Append("Person { ");
    sb.Append("Name = ");
    sb.Append(Name ?? "null");  // 澶勭悊 null
    sb.Append(" }");
    return sb.ToString();
}

鎶€宸?3锛氭牸寮忓寲涓嶅悓绫诲瀷

csharp
// 涓轰笉鍚岀被鍨嬬敓鎴愪笉鍚岀殑鏍煎紡鍖栦唬鐮?
private static string GenerateFormatCode(PropertyInfo property)
{
    return property.Type switch
    {
        "string" => $"sb.Append({property.Name} ?? \"null\");",
        "int" or "long" or "short" => $"sb.Append({property.Name});",
        "decimal" or "double" or "float" => $"sb.Append({property.Name}:F2);",
        "DateTime" => $"sb.Append({property.Name}:yyyy-MM-dd);",
        _ => $"sb.Append({property.Name});",
    };
}

鎶€宸?4锛氬鐞嗛泦鍚堢被鍨?

csharp
// 妫€娴嬮泦鍚堢被鍨?
private static bool IsCollectionType(ITypeSymbol type)
{
    return type.AllInterfaces.Any(i => 
        i.ToDisplayString().StartsWith("System.Collections.Generic.IEnumerable<"));
}

// 鐢熸垚闆嗗悎鏍煎紡鍖栦唬鐮?
if (IsCollectionType(property.Type))
{
    sb.AppendLine($"sb.Append(\"[\");");
    sb.AppendLine($"sb.Append(string.Join(\", \", {property.Name} ?? Array.Empty<object>()));");
    sb.AppendLine($"sb.Append(\"]\");");
}

鎶€宸?5锛氱缉杩涘拰鏍煎紡鍖?

csharp
// 鐢熸垚鏍煎紡鑹ソ鐨勪唬鐮?
private static void GenerateMethod(StringBuilder sb, ClassInfo classInfo, int indent = 0)
{
    var indentStr = new string(' ', indent * 4);
    
    sb.AppendLine($"{indentStr}public override string ToString()");
    sb.AppendLine($"{indentStr}{{");
    sb.AppendLine($"{indentStr}    var sb = new StringBuilder();");
    // ...
    sb.AppendLine($"{indentStr}}}");
}

楂樼骇鍔熻兘

鍔熻兘 1锛氳嚜瀹氫箟鏍煎紡鍖?

csharp
[GenerateToString(Format = "{Name} ({Age} years old)")]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// 鐢熸垚鐨勪唬鐮侊細
public override string ToString()
{
    return $"{Name} ({Age} years old)";
}

瀹炵幇锛?

csharp
private static void GenerateCustomFormat(
    StringBuilder sb,
    ClassInfo classInfo,
    string format)
{
    sb.AppendLine("public override string ToString()");
    sb.AppendLine("{");
    
    // 鏇挎崲鏍煎紡瀛楃涓蹭腑鐨勫崰浣嶇
    var formattedString = format;
    foreach (var prop in classInfo.Properties)
    {
        formattedString = formattedString.Replace(
            $"{{{prop.Name}}}",
            $"{{{prop.Name}}}"
        );
    }
    
    sb.AppendLine($"    return $\"{formattedString}\";");
    sb.AppendLine("}");
}

鍔熻兘 2锛氬鐞嗗祵濂楀璞?

csharp
[GenerateToString]
public partial class Order
{
    public int Id { get; set; }
    public Person Customer { get; set; }  // 宓屽瀵硅薄
    public List<Product> Items { get; set; }  // 闆嗗悎
}

// 鐢熸垚鐨勪唬鐮侊細
public override string ToString()
{
    var sb = new StringBuilder();
    sb.Append("Order { ");
    sb.Append("Id = ");
    sb.Append(Id);
    sb.Append(", Customer = ");
    sb.Append(Customer?.ToString() ?? "null");  // 璋冪敤宓屽瀵硅薄鐨?ToString
    sb.Append(", Items = [");
    sb.Append(string.Join(", ", Items?.Select(x => x.ToString()) ?? Array.Empty<string>()));
    sb.Append("]");
    sb.Append(" }");
    return sb.ToString();
}

鍔熻兘 3锛氭敮鎸佺户鎵?

csharp
public class BaseEntity
{
    public int Id { get; set; }
    public DateTime CreatedAt { get; set; }
}

[GenerateToString(IncludeBaseProperties = true)]
public partial class Product : BaseEntity
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// 鐢熸垚鐨勪唬鐮佸寘鍚熀绫诲睘鎬э細
public override string ToString()
{
    var sb = new StringBuilder();
    sb.Append("Product { ");
    sb.Append("Id = ");
    sb.Append(Id);  // 鍩虹被灞炴€?
    sb.Append(", CreatedAt = ");
    sb.Append(CreatedAt);  // 鍩虹被灞炴€?
    sb.Append(", Name = ");
    sb.Append(Name);
    sb.Append(", Price = ");
    sb.Append(Price);
    sb.Append(" }");
    return sb.ToString();
}

瀹炵幇锛?

csharp
private static ImmutableArray<PropertyInfo> GetAllProperties(
    INamedTypeSymbol symbol,
    bool includeBase)
{
    var properties = symbol.GetMembers()
        .OfType<IPropertySymbol>()
        .Where(p => p.DeclaredAccessibility == Accessibility.Public)
        .ToList();

    if (includeBase && symbol.BaseType != null)
    {
        var baseProperties = GetAllProperties(symbol.BaseType, true);
        properties.InsertRange(0, baseProperties);
    }

    return properties
        .Select(p => new PropertyInfo(p.Name, p.Type.ToDisplayString(), false))
        .ToImmutableArray();
}

鍔熻兘 4锛氬琛屾牸寮?

csharp
[GenerateToString(MultiLine = true)]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

// 鐢熸垚鐨勪唬鐮侊細
public override string ToString()
{
    var sb = new StringBuilder();
    sb.AppendLine("Person {");
    sb.AppendLine($"  Name = {Name},");
    sb.AppendLine($"  Age = {Age},");
    sb.AppendLine($"  Email = {Email}");
    sb.Append("}");
    return sb.ToString();
}

// 杈撳嚭锛?
// Person {
//   Name = John,
//   Age = 30,
//   Email = john@example.com
// }

鐪熷疄搴旂敤鍦烘櫙

鍦烘櫙 1锛氭棩蹇楄褰?

**闇€姹?*锛氬湪鏃ュ織涓褰曞璞$姸鎬侊紝鏂逛究璋冭瘯銆?

csharp
[GenerateToString]
public partial class HttpRequest
{
    public string Method { get; set; }
    public string Url { get; set; }
    public Dictionary<string, string> Headers { get; set; }
    
    [ToStringIgnore]
    public string AuthToken { get; set; }  // 鏁忔劅淇℃伅锛屼笉璁板綍
}

// 浣跨敤
var request = new HttpRequest
{
    Method = "POST",
    Url = "/api/users",
    Headers = new Dictionary<string, string>
    {

基于 MIT 许可发布