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>
{