From 40814f5cb96515539cfd33a9bca39cb451d6938c Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Tue, 18 Nov 2025 20:58:09 +0500 Subject: [PATCH 01/16] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/ObjectPrinter.cs | 11 ++- ObjectPrinting/PrintingConfig.cs | 91 ++++++++++++++++-------- ObjectPrinting/PropertyPrintingConfig.cs | 41 +++++++++++ 3 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 ObjectPrinting/PropertyPrintingConfig.cs diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..b2ce90a79 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,9 @@ -namespace ObjectPrinting +namespace ObjectPrinting; + +public class ObjectPrinter { - public class ObjectPrinter + public static PrintingConfig For() { - public static PrintingConfig For() - { - return new PrintingConfig(); - } + return new PrintingConfig(); } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..d154a4709 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,74 @@ using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig { - public class PrintingConfig + private readonly HashSet _excludedTypes = []; + private readonly HashSet _excludedMembers = []; + private readonly Dictionary> _typeSerializers = []; + private readonly Dictionary> _memberSerializers = []; + private readonly Dictionary _typeCultures = []; + private readonly Dictionary _memberCultures = []; + private readonly Dictionary _memberStringMaxLength = []; + private readonly int? _defaultStringMaxLength; + + public PrintingConfig Excluding() + { + return this; + } + + public PrintingConfig Excluding(Expression> memberSelector) { - public string PrintToString(TOwner obj) + return this; + } + + public PropertyPrintingConfig Printing() + { + return new PropertyPrintingConfig(this, null); + } + + public PropertyPrintingConfig Printing(Expression> memberSelector) + { + return null!; + // return new PropertyPrintingConfig(this, /*MemberInfo */); + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 0); + } + + private string PrintToString(object obj, int nestingLevel) + { + //TODO apply configurations + if (obj == null) + return "null" + Environment.NewLine; + + var finalTypes = new[] { - return PrintToString(obj, 0); - } + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + if (finalTypes.Contains(obj.GetType())) + return obj + Environment.NewLine; - private string PrintToString(object obj, int nestingLevel) + var identation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + var type = obj.GetType(); + sb.AppendLine(type.Name); + foreach (var propertyInfo in type.GetProperties()) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; - - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); + sb.Append(identation + propertyInfo.Name + " = " + + PrintToString(propertyInfo.GetValue(obj), + nestingLevel + 1)); } + return sb.ToString(); } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 000000000..fcb4d9d62 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; +using System.Reflection; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig(PrintingConfig printingConfig, MemberInfo? memberInfo) +{ + private readonly PrintingConfig _parentConfig = printingConfig; + private readonly MemberInfo? _memberInfo = memberInfo; + + public PrintingConfig Using(Func serializer) + { + /* + * Если _memberInfo null, тогда работаем со всеми значениями типа TProp + * Иначе только с конкретным свойством + */ + + return _parentConfig; + } + + public PrintingConfig Using(CultureInfo c) + { + /* + * Если _memberInfo null, тогда работаем со всеми значениями типа TProp + * Иначе только с конкретным свойством + */ + + return _parentConfig; + } + + public PrintingConfig TrimmedToLength(int len) + { + /* + * Если _memberInfo null, тогда устанавливаем длину для обычной строки + * Иначе только для конткретного свойства + */ + + return _parentConfig; + } +} From cbc3f74fa09ca1176fec556298464c15f8c5bdf1 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Wed, 19 Nov 2025 18:50:42 +0500 Subject: [PATCH 02/16] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20PrintingConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 98 +++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 21 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index d154a4709..21f052891 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,7 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; @@ -14,18 +12,21 @@ public class PrintingConfig private readonly HashSet _excludedMembers = []; private readonly Dictionary> _typeSerializers = []; private readonly Dictionary> _memberSerializers = []; - private readonly Dictionary _typeCultures = []; - private readonly Dictionary _memberCultures = []; - private readonly Dictionary _memberStringMaxLength = []; - private readonly int? _defaultStringMaxLength; public PrintingConfig Excluding() { + _excludedTypes.Add(typeof(TPropType)); return this; } public PrintingConfig Excluding(Expression> memberSelector) { + ArgumentNullException.ThrowIfNull(memberSelector); + + if (memberSelector.Body is not MemberExpression expression) + throw new ArgumentException(null, nameof(memberSelector)); + + _excludedMembers.Add(expression.Member); return this; } @@ -36,39 +37,94 @@ public PropertyPrintingConfig Printing() public PropertyPrintingConfig Printing(Expression> memberSelector) { - return null!; - // return new PropertyPrintingConfig(this, /*MemberInfo */); + ArgumentNullException.ThrowIfNull(memberSelector); + + if (memberSelector.Body is not MemberExpression expression) + throw new ArgumentException(null, nameof(memberSelector)); + + return new PropertyPrintingConfig(this, expression.Member); } public string PrintToString(TOwner obj) { - return PrintToString(obj, 0); + var visited = new HashSet(); + return PrintToString(obj, 0, visited, null); } - private string PrintToString(object obj, int nestingLevel) + private string PrintToString(object obj, int nestingLevel, HashSet visited, MemberInfo? member) { - //TODO apply configurations if (obj == null) return "null" + Environment.NewLine; - var finalTypes = new[] + var type = obj.GetType(); + + if (!IsFinalType(type)) { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) + if (visited.Contains(type)) + return "Cycle ref" + Environment.NewLine; + + visited.Add(obj); + } + + if (member != null && _memberSerializers.TryGetValue(type, out var memberSerializer)) + return memberSerializer(obj) + Environment.NewLine; + + if (_typeSerializers.TryGetValue(type, out var typeSerializer)) + return typeSerializer(obj) + Environment.NewLine; + + if (IsFinalType(type)) return obj + Environment.NewLine; var identation = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); - var type = obj.GetType(); + sb.AppendLine(type.Name); + foreach (var propertyInfo in type.GetProperties()) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + if (ShouldSkipMember(propertyInfo)) + continue; + + var val = propertyInfo.GetValue(obj); + sb.Append( + identation + + propertyInfo.Name + + " = " + + PrintToString(val, nestingLevel + 1, visited, propertyInfo) + ); } + return sb.ToString(); } + + private static bool IsFinalType(Type type) + { + return type.IsPrimitive + || type == typeof(string) + || type == typeof(decimal) + || type == typeof(DateTime) + || type == typeof(TimeSpan) + || type == typeof(Guid); + } + + private bool ShouldSkipMember(MemberInfo member) + { + if (_excludedMembers.Contains(member)) + return true; + + var type = member switch + { + PropertyInfo p => p.PropertyType, + FieldInfo f => f.FieldType, + _ => null + }; + + if (type == null) + return false; + + if (_excludedTypes.Contains(type)) + return true; + + return false; + } } \ No newline at end of file From 43a7c9dd69e31bc22b5a00f53d128f0647a7602f Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Thu, 20 Nov 2025 08:46:46 +0500 Subject: [PATCH 03/16] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20Printing=20config=20=D0=B8=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20PropertyPrin?= =?UTF-8?q?tingConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/ObjectPrinting.csproj | 1 + ObjectPrinting/PrintingConfig.cs | 129 +++++++++++++++----- ObjectPrinting/PropertyPrintingConfig.cs | 41 ++++--- ObjectPrinting/ReferenceEqualityComparer.cs | 17 +++ 4 files changed, 136 insertions(+), 52 deletions(-) create mode 100644 ObjectPrinting/ReferenceEqualityComparer.cs diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..ea98111e3 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,6 +5,7 @@ + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 21f052891..dbffdf6f0 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Text; @@ -12,6 +13,45 @@ public class PrintingConfig private readonly HashSet _excludedMembers = []; private readonly Dictionary> _typeSerializers = []; private readonly Dictionary> _memberSerializers = []; + private readonly Dictionary _typeCultures = []; + private readonly Dictionary _memberStringTrims = []; + private readonly Dictionary _typeStringTrims = []; + + internal void SetTypeSerializer(Type type, Func serializer) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(serializer); + + _typeSerializers[type] = serializer; + } + + internal void SetMemberSerializer(MemberInfo member, Func serializer) + { + ArgumentNullException.ThrowIfNull(member); + ArgumentNullException.ThrowIfNull(serializer); + + _memberSerializers[member] = serializer; + } + + internal void SetTypeCulture(Type type, CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(culture); + + _typeCultures[type] = culture; + } + + internal void SetMemberStringTrim(MemberInfo member, int maxLength) + { + ArgumentNullException.ThrowIfNull(member); + _memberStringTrims[member] = maxLength; + } + + internal void SetTypeStringTrim(Type type, int maxLength) + { + ArgumentNullException.ThrowIfNull(type); + _typeStringTrims[type] = maxLength; + } public PrintingConfig Excluding() { @@ -32,7 +72,7 @@ public PrintingConfig Excluding(Expression> m public PropertyPrintingConfig Printing() { - return new PropertyPrintingConfig(this, null); + return new PropertyPrintingConfig(this, typeof(TProp), null); } public PropertyPrintingConfig Printing(Expression> memberSelector) @@ -42,56 +82,53 @@ public PropertyPrintingConfig Printing(Expression(this, expression.Member); + return new PropertyPrintingConfig(this, typeof(TProp), expression.Member); } public string PrintToString(TOwner obj) { - var visited = new HashSet(); - return PrintToString(obj, 0, visited, null); + var visited = new HashSet(new ReferenceEqualityComparer()); + return PrintToString(obj!, 0, visited, null!); } - private string PrintToString(object obj, int nestingLevel, HashSet visited, MemberInfo? member) + private string PrintToString(object obj, int nestingLevel, HashSet visited, MemberInfo member) { if (obj == null) return "null" + Environment.NewLine; var type = obj.GetType(); - if (!IsFinalType(type)) - { - if (visited.Contains(type)) - return "Cycle ref" + Environment.NewLine; - - visited.Add(obj); - } - - if (member != null && _memberSerializers.TryGetValue(type, out var memberSerializer)) + if (member != null && _memberSerializers.TryGetValue(member, out var memberSerializer)) return memberSerializer(obj) + Environment.NewLine; if (_typeSerializers.TryGetValue(type, out var typeSerializer)) return typeSerializer(obj) + Environment.NewLine; if (IsFinalType(type)) - return obj + Environment.NewLine; + { + var text = FormatFinalValue(obj, type, member); + return text + Environment.NewLine; + } - var identation = new string('\t', nestingLevel + 1); + if (!visited.Add(obj)) + return "cyclic reference" + Environment.NewLine; + + var indent = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine(type.Name); - - foreach (var propertyInfo in type.GetProperties()) + + foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - if (ShouldSkipMember(propertyInfo)) + if (ShouldSkipMember(property)) continue; - var val = propertyInfo.GetValue(obj); - sb.Append( - identation - + propertyInfo.Name - + " = " - + PrintToString(val, nestingLevel + 1, visited, propertyInfo) - ); + var value = property.GetValue(obj); + + sb.Append(indent) + .Append(property.Name) + .Append(" = ") + .Append(PrintToString(value, nestingLevel + 1, visited, property)); } return sb.ToString(); @@ -107,6 +144,38 @@ private static bool IsFinalType(Type type) || type == typeof(Guid); } + private string FormatFinalValue(object obj, Type type, MemberInfo member) + { + string result; + + if (obj is IFormattable formattable && _typeCultures.TryGetValue(type, out var culture)) + result = formattable.ToString(null, culture) ?? string.Empty; + else + result = obj.ToString() ?? string.Empty; + + if (type == typeof(string)) + { + var needsTrim = false; + var maxLength = 0; + + if (member != null && _memberStringTrims.TryGetValue(member, out var memberMax)) + { + needsTrim = result.Length > memberMax; + maxLength = memberMax; + } + else if (_typeStringTrims.TryGetValue(type, out var typeMax)) + { + needsTrim = result.Length > typeMax; + maxLength = typeMax; + } + + if (needsTrim) + result = result[..maxLength]; + } + + return result; + } + private bool ShouldSkipMember(MemberInfo member) { if (_excludedMembers.Contains(member)) @@ -119,12 +188,6 @@ private bool ShouldSkipMember(MemberInfo member) _ => null }; - if (type == null) - return false; - - if (_excludedTypes.Contains(type)) - return true; - - return false; + return type != null && _excludedTypes.Contains(type); } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index fcb4d9d62..0f95aa67c 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -4,38 +4,41 @@ namespace ObjectPrinting; -public class PropertyPrintingConfig(PrintingConfig printingConfig, MemberInfo? memberInfo) +public class PropertyPrintingConfig(PrintingConfig printingConfig, Type targetType, MemberInfo? memberInfo) { - private readonly PrintingConfig _parentConfig = printingConfig; + private readonly PrintingConfig _printingConfig = printingConfig; + private readonly Type _targetType = targetType; private readonly MemberInfo? _memberInfo = memberInfo; public PrintingConfig Using(Func serializer) { - /* - * Если _memberInfo null, тогда работаем со всеми значениями типа TProp - * Иначе только с конкретным свойством - */ + ArgumentNullException.ThrowIfNull(serializer); - return _parentConfig; + string boxed(object o) => serializer((TProp)o); + + if (_memberInfo != null) + _printingConfig.SetMemberSerializer(_memberInfo, boxed); + else + _printingConfig.SetTypeSerializer(_targetType, boxed); + + return _printingConfig; } - public PrintingConfig Using(CultureInfo c) + public PrintingConfig Using(CultureInfo culture) { - /* - * Если _memberInfo null, тогда работаем со всеми значениями типа TProp - * Иначе только с конкретным свойством - */ + ArgumentNullException.ThrowIfNull(culture); - return _parentConfig; + _printingConfig.SetTypeCulture(_targetType, culture); + return _printingConfig; } - public PrintingConfig TrimmedToLength(int len) + public PrintingConfig TrimmedToLength(int maxLength) { - /* - * Если _memberInfo null, тогда устанавливаем длину для обычной строки - * Иначе только для конткретного свойства - */ + if (_memberInfo != null) + _printingConfig.SetMemberStringTrim(_memberInfo, maxLength); + else + _printingConfig.SetTypeStringTrim(_targetType, maxLength); - return _parentConfig; + return _printingConfig; } } diff --git a/ObjectPrinting/ReferenceEqualityComparer.cs b/ObjectPrinting/ReferenceEqualityComparer.cs new file mode 100644 index 000000000..3e461004a --- /dev/null +++ b/ObjectPrinting/ReferenceEqualityComparer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace ObjectPrinting; + +public class ReferenceEqualityComparer : IEqualityComparer +{ + public new bool Equals(object? x, object? y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(object obj) + { + return obj.GetHashCode(); + } +} + From dccd8770835737d6924792181041b7f4ea86e69f Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Fri, 21 Nov 2025 09:06:28 +0500 Subject: [PATCH 04/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D0=BF=D1=80=D0=BE=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE=20=D0=BF=D1=83?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=87=D0=BD=D1=8B=D0=BC=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index dbffdf6f0..3c91815f3 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -118,22 +118,41 @@ private string PrintToString(object obj, int nestingLevel, HashSet visit sb.AppendLine(type.Name); - foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + foreach (var memberInfo in GetSerializableMembers(type)) { - if (ShouldSkipMember(property)) + if (ShouldSkipMember(memberInfo)) continue; - var value = property.GetValue(obj); + var value = GetMemberValue(obj, memberInfo); sb.Append(indent) - .Append(property.Name) + .Append(memberInfo.Name) .Append(" = ") - .Append(PrintToString(value, nestingLevel + 1, visited, property)); + .Append(PrintToString(value!, nestingLevel + 1, visited, memberInfo)); } return sb.ToString(); } + private static IEnumerable GetSerializableMembers(Type type) + { + foreach (var member in type.GetFields()) + yield return member; + + foreach (var member in type.GetProperties()) + yield return member; + } + + private static object? GetMemberValue(object obj, MemberInfo member) + { + return member switch + { + FieldInfo field => field.GetValue(obj), + PropertyInfo property => property.GetValue(obj), + _ => null + }; + } + private static bool IsFinalType(Type type) { return type.IsPrimitive From e6573d9a1b127dce9d2ec178b36e8d86683675fd Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sat, 22 Nov 2025 09:48:09 +0500 Subject: [PATCH 05/16] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=B8?= =?UTF-8?q?=D0=B7=20Solved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/Solved/ObjectExtensions.cs | 10 --- ObjectPrinting/Solved/ObjectPrinter.cs | 10 --- ObjectPrinting/Solved/PrintingConfig.cs | 62 ------------------- .../Solved/PropertyPrintingConfig.cs | 32 ---------- .../PropertyPrintingConfigExtensions.cs | 18 ------ .../Tests/ObjectPrinterAcceptanceTests.cs | 40 ------------ ObjectPrinting/Solved/Tests/Person.cs | 12 ---- 7 files changed, 184 deletions(-) delete mode 100644 ObjectPrinting/Solved/ObjectExtensions.cs delete mode 100644 ObjectPrinting/Solved/ObjectPrinter.cs delete mode 100644 ObjectPrinting/Solved/PrintingConfig.cs delete mode 100644 ObjectPrinting/Solved/PropertyPrintingConfig.cs delete mode 100644 ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs delete mode 100644 ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs delete mode 100644 ObjectPrinting/Solved/Tests/Person.cs diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553c..000000000 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public static class ObjectExtensions - { - public static string PrintToString(this T obj) - { - return ObjectPrinter.For().PrintToString(obj); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectPrinter.cs b/ObjectPrinting/Solved/ObjectPrinter.cs deleted file mode 100644 index 540ee769c..000000000 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index 0ec5aeb2b..000000000 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Text; - -namespace ObjectPrinting.Solved -{ - public class PrintingConfig - { - public PropertyPrintingConfig Printing() - { - return new PropertyPrintingConfig(this); - } - - public PropertyPrintingConfig Printing(Expression> memberSelector) - { - return new PropertyPrintingConfig(this); - } - - public PrintingConfig Excluding(Expression> memberSelector) - { - return this; - } - - internal PrintingConfig Excluding() - { - return this; - } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - private string PrintToString(object obj, int nestingLevel) - { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; - - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs deleted file mode 100644 index a509697d1..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; - -namespace ObjectPrinting.Solved -{ - public class PropertyPrintingConfig : IPropertyPrintingConfig - { - private readonly PrintingConfig printingConfig; - - public PropertyPrintingConfig(PrintingConfig printingConfig) - { - this.printingConfig = printingConfig; - } - - public PrintingConfig Using(Func print) - { - return printingConfig; - } - - public PrintingConfig Using(CultureInfo culture) - { - return printingConfig; - } - - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; - } - - public interface IPropertyPrintingConfig - { - PrintingConfig ParentConfig { get; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs deleted file mode 100644 index dd3922394..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved -{ - public static class PropertyPrintingConfigExtensions - { - public static string PrintToString(this T obj, Func, PrintingConfig> config) - { - return config(ObjectPrinter.For()).PrintToString(obj); - } - - public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) - { - return ((IPropertyPrintingConfig)propConfig).ParentConfig; - } - - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index ac52d5ee5..000000000 --- a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; - -namespace ObjectPrinting.Solved.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For() - //1. Исключить из сериализации свойства определенного типа - .Excluding() - //2. Указать альтернативный способ сериализации для определенного типа - .Printing().Using(i => i.ToString("X")) - //3. Для числовых типов указать культуру - .Printing().Using(CultureInfo.InvariantCulture) - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .Printing(p => p.Name).TrimmedToLength(10) - //6. Исключить из сериализации конкретного свойства - .Excluding(p => p.Age); - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - - //8. ...с конфигурированием - string s3 = person.PrintToString(s => s.Excluding(p => p.Age)); - Console.WriteLine(s1); - Console.WriteLine(s2); - Console.WriteLine(s3); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/Person.cs b/ObjectPrinting/Solved/Tests/Person.cs deleted file mode 100644 index 858ebbf8d..000000000 --- a/ObjectPrinting/Solved/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file From 2a6481a8c11d9fde77068e1724d9f9083a415480 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sat, 22 Nov 2025 11:11:19 +0500 Subject: [PATCH 06/16] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=B8?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D1=80=D0=B0=D1=81?= =?UTF-8?q?=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D1=81=D0=B5=D1=80=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BF=D0=BE-=D1=83=D0=BC=D0=BE=D0=BB=D1=87?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8E=20=D0=B8=20=D1=81=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/ObjectPrintingExtensions.cs | 16 ++ .../Tests/ObjectPrinterAcceptanceTests.cs | 139 +++++++++++++++--- ObjectPrinting/Tests/Person.cs | 22 ++- 3 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 ObjectPrinting/ObjectPrintingExtensions.cs diff --git a/ObjectPrinting/ObjectPrintingExtensions.cs b/ObjectPrinting/ObjectPrintingExtensions.cs new file mode 100644 index 000000000..059637a37 --- /dev/null +++ b/ObjectPrinting/ObjectPrintingExtensions.cs @@ -0,0 +1,16 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrintingExtensions +{ + public static string PrintToString(this TOwner obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this TOwner obj, Func, PrintingConfig> config) + { + return config(ObjectPrinter.For()).PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..d96921bf8 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,124 @@ -using NUnit.Framework; +using FluentAssertions; +using NUnit.Framework; +using System; +using static System.Globalization.CultureInfo; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests { - [TestFixture] - public class ObjectPrinterAcceptanceTests + private const int TrimmedLength = 2; + + private Person _person = null!; + private PrintingConfig _printer = null!; + + [SetUp] + public void SetUp() { - [Test] - public void Demo() + _person = new Person { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } + Id = Guid.NewGuid(), + Name = "Mikasa", + Age = 19, + Height = 1.76, + Gender = "Female" + }; + + _printer = ObjectPrinter.For(); + } + + [Test] + public void PrintToStringExtension_DefaultConfiguration_DefaultSerialization() + { + var result = _person.PrintToString(); + + result.Should().Contain("Mikasa"); + result.Should().Contain("19"); + result.Should().Contain("1,76"); + } + + [Test] + public void PrintToStringExtension_WithCustomConfiguration_ApplyCustomConfiguration() + { + var result = _person.PrintToString(cfg => cfg.Printing(p => p.Name).TrimmedToLength(TrimmedLength).Printing(p => p.Height).Using(InvariantCulture)); + + result.Should().Contain("Mi"); + result.Should().Contain("1.76"); + } + + [Test] + public void PrintToString_ExcludingType_SkipAllMembersOfThisType() + { + _printer = _printer.Excluding(); + + var result = _printer.PrintToString(_person); + + result.Should().NotContain(nameof(_person.Name)); + result.Should().NotContain(nameof(_person.Gender)); + } + + [Test] + public void PrintToString_UsingCustomSerializerForType_ApplySerializerToAllMembersOfType() + { + _printer = _printer.Printing().Using(i => $"Целое число: {i}"); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("Целое число: 19"); + } + + [Test] + public void PrintToString_SetCultureForNumericType_ApplyCultureToSerialization() + { + _printer = _printer.Printing().Using(InvariantCulture); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("1.76"); + } + + [Test] + public void PrintToString_CustomSerializerForProperty_ChangeOnlyThisProperty() + { + _printer = _printer.Printing(p => p.Age).Using(a => $"Возраст человека: {a}"); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("Возраст человека: 19"); + } + + [Test] + public void PrintToString_TrimProperty_TrimPropertyCorrectly() + { + _printer = _printer.Printing(p => p.Name).TrimmedToLength(TrimmedLength); + + var result = _printer.PrintToString(_person); + + result.Should().Contain("Mi"); + } + + [Test] + public void PrintToString_ExcludeProperty_SkipThisProperty() + { + _printer = _printer.Excluding(p => p.Height); + + var result = _printer.PrintToString(_person); + + result.Should().NotContain("Height"); + } + + [Test] + public void PrintToString_CyclicReference_ShouldNotCallStackOverflow() + { + var parent = new Person { Name = "Grisha" }; + var child = new Person { Name = "Eren" }; + + parent.Child = child; + child.Child = parent; + + var result = _printer.PrintToString(parent); + + result.Should().Contain("cyclic reference"); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..055e2e3fc 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +1,18 @@ using System; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +public class Person { - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } + public Guid Id { get; set; } + + public string? Name { get; set; } + + public double Height { get; set; } + + public int Age { get; set; } + + public Person? Child { get; set; } + + public string? Gender; } \ No newline at end of file From ea530aa3f13a7014ed1f8d49d3b4e18b9a43889b Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sun, 14 Dec 2025 15:48:44 +0500 Subject: [PATCH 07/16] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=BF=D0=BE=20null=20=D0=B8=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D1=80=D0=B0=D0=B7=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BA=D1=83=D0=BB=D1=8C=D1=82=D1=83=D1=80=D1=8B=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B7=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D0=B8=20=D1=81=D0=B2=D0=BE=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 42 +++++++++++++++++++----- ObjectPrinting/PropertyPrintingConfig.cs | 6 +++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 3c91815f3..e7abf4ab3 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -14,6 +14,7 @@ public class PrintingConfig private readonly Dictionary> _typeSerializers = []; private readonly Dictionary> _memberSerializers = []; private readonly Dictionary _typeCultures = []; + private readonly Dictionary _memberCultures = []; private readonly Dictionary _memberStringTrims = []; private readonly Dictionary _typeStringTrims = []; @@ -41,6 +42,14 @@ internal void SetTypeCulture(Type type, CultureInfo culture) _typeCultures[type] = culture; } + internal void SetMemberCulture(MemberInfo member, CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(member); + ArgumentNullException.ThrowIfNull(culture); + + _memberCultures[member] = culture; + } + internal void SetMemberStringTrim(MemberInfo member, int maxLength) { ArgumentNullException.ThrowIfNull(member); @@ -88,13 +97,13 @@ public PropertyPrintingConfig Printing(Expression(new ReferenceEqualityComparer()); - return PrintToString(obj!, 0, visited, null!); + return PrintToString(obj, 0, visited, member: null, expectedType: typeof(TOwner)); } - private string PrintToString(object obj, int nestingLevel, HashSet visited, MemberInfo member) + private string PrintToString(object? obj, int nestingLevel, HashSet visited, MemberInfo? member, Type expectedType) { if (obj == null) - return "null" + Environment.NewLine; + return $"null ({expectedType.Name}){Environment.NewLine}"; var type = obj.GetType(); @@ -117,23 +126,34 @@ private string PrintToString(object obj, int nestingLevel, HashSet visit var sb = new StringBuilder(); sb.AppendLine(type.Name); - + foreach (var memberInfo in GetSerializableMembers(type)) { if (ShouldSkipMember(memberInfo)) continue; var value = GetMemberValue(obj, memberInfo); + var memberType = GetMemberType(memberInfo); sb.Append(indent) .Append(memberInfo.Name) .Append(" = ") - .Append(PrintToString(value!, nestingLevel + 1, visited, memberInfo)); + .Append(PrintToString(value, nestingLevel + 1, visited, memberInfo, memberType)); } return sb.ToString(); } + private static Type GetMemberType(MemberInfo member) + { + return member switch + { + FieldInfo f => f.FieldType, + PropertyInfo p => p.PropertyType, + _ => typeof(object) + }; + } + private static IEnumerable GetSerializableMembers(Type type) { foreach (var member in type.GetFields()) @@ -163,11 +183,17 @@ private static bool IsFinalType(Type type) || type == typeof(Guid); } - private string FormatFinalValue(object obj, Type type, MemberInfo member) + private string FormatFinalValue(object obj, Type type, MemberInfo? member) { string result; - if (obj is IFormattable formattable && _typeCultures.TryGetValue(type, out var culture)) + CultureInfo? culture = null; + if (member != null && _memberCultures.TryGetValue(member, out var memberCulture)) + culture = memberCulture; + else if (_typeCultures.TryGetValue(type, out var typeCulture)) + culture = typeCulture; + + if (obj is IFormattable formattable && culture != null) result = formattable.ToString(null, culture) ?? string.Empty; else result = obj.ToString() ?? string.Empty; @@ -209,4 +235,4 @@ private bool ShouldSkipMember(MemberInfo member) return type != null && _excludedTypes.Contains(type); } -} \ No newline at end of file +} diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index 0f95aa67c..9b4821e0f 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -28,7 +28,11 @@ public PrintingConfig Using(CultureInfo culture) { ArgumentNullException.ThrowIfNull(culture); - _printingConfig.SetTypeCulture(_targetType, culture); + if (_memberInfo != null) + _printingConfig.SetMemberCulture(_memberInfo, culture); + else + _printingConfig.SetTypeCulture(_targetType, culture); + return _printingConfig; } From 7ad547275fad0fd713758f2590b788334e9f4b0a Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sun, 14 Dec 2025 15:56:41 +0500 Subject: [PATCH 08/16] =?UTF-8?q?=D0=98=D1=81=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20Excluding=20=D0=B8=20Printi?= =?UTF-8?q?ng=20=D1=81=20=D0=BF=D0=BE=D0=BD=D1=8F=D1=82=D0=BD=D1=8B=D0=BC?= =?UTF-8?q?=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index e7abf4ab3..daf1703be 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -73,7 +73,7 @@ public PrintingConfig Excluding(Expression> m ArgumentNullException.ThrowIfNull(memberSelector); if (memberSelector.Body is not MemberExpression expression) - throw new ArgumentException(null, nameof(memberSelector)); + throw new ArgumentException("Выражение должно указывать на поле или свойство.", nameof(memberSelector)); _excludedMembers.Add(expression.Member); return this; @@ -89,7 +89,7 @@ public PropertyPrintingConfig Printing(Expression(this, typeof(TProp), expression.Member); } From 30f656fc5028a98b018e990e136e6f391b4aa6c7 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sun, 14 Dec 2025 17:58:14 +0500 Subject: [PATCH 09/16] =?UTF-8?q?=D0=9D=D0=B5=20=D1=83=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D0=B5=D0=BC=20=D1=81=D0=B2=D0=BE=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B0=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D1=83=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=87=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B3=D0=B5=D1=82?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index daf1703be..6d83fc8c5 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -156,11 +156,19 @@ private static Type GetMemberType(MemberInfo member) private static IEnumerable GetSerializableMembers(Type type) { - foreach (var member in type.GetFields()) + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + + foreach (var member in type.GetFields(flags)) yield return member; - foreach (var member in type.GetProperties()) + foreach (var member in type.GetProperties(flags)) + { + var getter = member.GetMethod; + if (getter is null || !getter.IsPublic) + continue; + yield return member; + } } private static object? GetMemberValue(object obj, MemberInfo member) From b6eb7323269d727885d92a1b9cb3c6c05eac1e8f Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sun, 14 Dec 2025 18:49:25 +0500 Subject: [PATCH 10/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/IPrintingConfig.cs | 17 +++++++++++++++++ ObjectPrinting/IPropertyPrintingConfig.cs | 13 +++++++++++++ ObjectPrinting/ObjectPrinter.cs | 6 +++--- ObjectPrinting/ObjectPrintingExtensions.cs | 2 +- ObjectPrinting/PrintingConfig.cs | 12 ++++++------ ObjectPrinting/PropertyPrintingConfig.cs | 8 ++++---- .../Tests/ObjectPrinterAcceptanceTests.cs | 2 +- 7 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 ObjectPrinting/IPrintingConfig.cs create mode 100644 ObjectPrinting/IPropertyPrintingConfig.cs diff --git a/ObjectPrinting/IPrintingConfig.cs b/ObjectPrinting/IPrintingConfig.cs new file mode 100644 index 000000000..d8eba174d --- /dev/null +++ b/ObjectPrinting/IPrintingConfig.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public interface IPrintingConfig +{ + IPrintingConfig Excluding(); + + IPrintingConfig Excluding(Expression> memberSelector); + + IPropertyPrintingConfig Printing(); + + IPropertyPrintingConfig Printing(Expression> memberSelector); + + string PrintToString(TOwner obj); +} diff --git a/ObjectPrinting/IPropertyPrintingConfig.cs b/ObjectPrinting/IPropertyPrintingConfig.cs new file mode 100644 index 000000000..f24ac17e8 --- /dev/null +++ b/ObjectPrinting/IPropertyPrintingConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting; + +public interface IPropertyPrintingConfig +{ + IPrintingConfig Using(Func serializer); + + IPrintingConfig Using(CultureInfo culture); + + IPrintingConfig TrimmedToLength(int maxLength); +} diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index b2ce90a79..f1eb49258 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,8 +1,8 @@ -namespace ObjectPrinting; +namespace ObjectPrinting; -public class ObjectPrinter +public static class ObjectPrinter { - public static PrintingConfig For() + public static IPrintingConfig For() { return new PrintingConfig(); } diff --git a/ObjectPrinting/ObjectPrintingExtensions.cs b/ObjectPrinting/ObjectPrintingExtensions.cs index 059637a37..19cabac7a 100644 --- a/ObjectPrinting/ObjectPrintingExtensions.cs +++ b/ObjectPrinting/ObjectPrintingExtensions.cs @@ -9,7 +9,7 @@ public static string PrintToString(this TOwner obj) return ObjectPrinter.For().PrintToString(obj); } - public static string PrintToString(this TOwner obj, Func, PrintingConfig> config) + public static string PrintToString(this TOwner obj, Func, IPrintingConfig> config) { return config(ObjectPrinter.For()).PrintToString(obj); } diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 6d83fc8c5..991bfafbc 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -7,7 +7,7 @@ namespace ObjectPrinting; -public class PrintingConfig +public class PrintingConfig : IPrintingConfig { private readonly HashSet _excludedTypes = []; private readonly HashSet _excludedMembers = []; @@ -62,13 +62,13 @@ internal void SetTypeStringTrim(Type type, int maxLength) _typeStringTrims[type] = maxLength; } - public PrintingConfig Excluding() + public IPrintingConfig Excluding() { _excludedTypes.Add(typeof(TPropType)); return this; } - public PrintingConfig Excluding(Expression> memberSelector) + public IPrintingConfig Excluding(Expression> memberSelector) { ArgumentNullException.ThrowIfNull(memberSelector); @@ -79,12 +79,12 @@ public PrintingConfig Excluding(Expression> m return this; } - public PropertyPrintingConfig Printing() + public IPropertyPrintingConfig Printing() { - return new PropertyPrintingConfig(this, typeof(TProp), null); + return new PropertyPrintingConfig(this, typeof(TProp), null!); } - public PropertyPrintingConfig Printing(Expression> memberSelector) + public IPropertyPrintingConfig Printing(Expression> memberSelector) { ArgumentNullException.ThrowIfNull(memberSelector); diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index 9b4821e0f..508752ee4 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -4,13 +4,13 @@ namespace ObjectPrinting; -public class PropertyPrintingConfig(PrintingConfig printingConfig, Type targetType, MemberInfo? memberInfo) +public class PropertyPrintingConfig(PrintingConfig printingConfig, Type targetType, MemberInfo memberInfo) : IPropertyPrintingConfig { private readonly PrintingConfig _printingConfig = printingConfig; private readonly Type _targetType = targetType; private readonly MemberInfo? _memberInfo = memberInfo; - public PrintingConfig Using(Func serializer) + public IPrintingConfig Using(Func serializer) { ArgumentNullException.ThrowIfNull(serializer); @@ -24,7 +24,7 @@ public PrintingConfig Using(Func serializer) return _printingConfig; } - public PrintingConfig Using(CultureInfo culture) + public IPrintingConfig Using(CultureInfo culture) { ArgumentNullException.ThrowIfNull(culture); @@ -36,7 +36,7 @@ public PrintingConfig Using(CultureInfo culture) return _printingConfig; } - public PrintingConfig TrimmedToLength(int maxLength) + public IPrintingConfig TrimmedToLength(int maxLength) { if (_memberInfo != null) _printingConfig.SetMemberStringTrim(_memberInfo, maxLength); diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index d96921bf8..79e53631a 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -11,7 +11,7 @@ public class ObjectPrinterAcceptanceTests private const int TrimmedLength = 2; private Person _person = null!; - private PrintingConfig _printer = null!; + private IPrintingConfig _printer = null!; [SetUp] public void SetUp() From 94c5cf1cdc3f77769482bbf98fcd8238fbc00821 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Sun, 14 Dec 2025 22:21:34 +0500 Subject: [PATCH 11/16] =?UTF-8?q?=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20PropertyPrintingConfig=20=D0=B2=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=20PrintingConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/IPrintingConfig.cs | 2 +- ObjectPrinting/IPropertyPrintingConfig.cs | 10 ++--- ObjectPrinting/PropertyPrintingConfig.cs | 54 +++++++++++++++++------ 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/ObjectPrinting/IPrintingConfig.cs b/ObjectPrinting/IPrintingConfig.cs index d8eba174d..c061ca227 100644 --- a/ObjectPrinting/IPrintingConfig.cs +++ b/ObjectPrinting/IPrintingConfig.cs @@ -14,4 +14,4 @@ public interface IPrintingConfig IPropertyPrintingConfig Printing(Expression> memberSelector); string PrintToString(TOwner obj); -} +} \ No newline at end of file diff --git a/ObjectPrinting/IPropertyPrintingConfig.cs b/ObjectPrinting/IPropertyPrintingConfig.cs index f24ac17e8..08d5247db 100644 --- a/ObjectPrinting/IPropertyPrintingConfig.cs +++ b/ObjectPrinting/IPropertyPrintingConfig.cs @@ -3,11 +3,11 @@ namespace ObjectPrinting; -public interface IPropertyPrintingConfig +public interface IPropertyPrintingConfig : IPrintingConfig { - IPrintingConfig Using(Func serializer); + IPropertyPrintingConfig Using(Func serializer); - IPrintingConfig Using(CultureInfo culture); + IPropertyPrintingConfig Using(CultureInfo culture); - IPrintingConfig TrimmedToLength(int maxLength); -} + IPropertyPrintingConfig TrimmedToLength(int maxLength); +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index 508752ee4..3f4642a10 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -1,48 +1,74 @@ using System; using System.Globalization; +using System.Linq.Expressions; using System.Reflection; namespace ObjectPrinting; -public class PropertyPrintingConfig(PrintingConfig printingConfig, Type targetType, MemberInfo memberInfo) : IPropertyPrintingConfig +public class PropertyPrintingConfig(PrintingConfig parent, Type targetType, MemberInfo memberInfo) : IPropertyPrintingConfig { - private readonly PrintingConfig _printingConfig = printingConfig; + private readonly PrintingConfig _parent = parent; private readonly Type _targetType = targetType; private readonly MemberInfo? _memberInfo = memberInfo; - public IPrintingConfig Using(Func serializer) + public IPropertyPrintingConfig Using(Func serializer) { ArgumentNullException.ThrowIfNull(serializer); string boxed(object o) => serializer((TProp)o); if (_memberInfo != null) - _printingConfig.SetMemberSerializer(_memberInfo, boxed); + _parent.SetMemberSerializer(_memberInfo, boxed); else - _printingConfig.SetTypeSerializer(_targetType, boxed); + _parent.SetTypeSerializer(_targetType, boxed); - return _printingConfig; + return this; } - public IPrintingConfig Using(CultureInfo culture) + public IPropertyPrintingConfig Using(CultureInfo culture) { ArgumentNullException.ThrowIfNull(culture); if (_memberInfo != null) - _printingConfig.SetMemberCulture(_memberInfo, culture); + _parent.SetMemberCulture(_memberInfo, culture); else - _printingConfig.SetTypeCulture(_targetType, culture); + _parent.SetTypeCulture(_targetType, culture); - return _printingConfig; + return this; } - public IPrintingConfig TrimmedToLength(int maxLength) + public IPropertyPrintingConfig TrimmedToLength(int maxLength) { if (_memberInfo != null) - _printingConfig.SetMemberStringTrim(_memberInfo, maxLength); + _parent.SetMemberStringTrim(_memberInfo, maxLength); else - _printingConfig.SetTypeStringTrim(_targetType, maxLength); + _parent.SetTypeStringTrim(_targetType, maxLength); - return _printingConfig; + return this; + } + + public IPrintingConfig Excluding() + { + return _parent.Excluding(); + } + + public IPrintingConfig Excluding(Expression> memberSelector) + { + return _parent.Excluding(memberSelector); + } + + public IPropertyPrintingConfig Printing() + { + return _parent.Printing(); + } + + public IPropertyPrintingConfig Printing(Expression> memberSelector) + { + return _parent.Printing(memberSelector); + } + + public string PrintToString(TOwner obj) + { + return _parent.PrintToString(obj); } } From 542b6416dbeaec9d851098db87830e68f145e1a0 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Mon, 15 Dec 2025 18:06:57 +0500 Subject: [PATCH 12/16] =?UTF-8?q?string=20builder=20=D1=81=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D1=82=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=BA=D0=BE=201=20=D1=80=D0=B0=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 56 ++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 991bfafbc..5a6cb9d3d 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -97,35 +97,58 @@ public IPropertyPrintingConfig Printing(Expression(new ReferenceEqualityComparer()); - return PrintToString(obj, 0, visited, member: null, expectedType: typeof(TOwner)); + var sb = new StringBuilder(); + + AppendTo(sb, obj!, 0, visited, null!, typeof(TOwner)); + + return sb.ToString(); } - private string PrintToString(object? obj, int nestingLevel, HashSet visited, MemberInfo? member, Type expectedType) + private void AppendTo(StringBuilder sb, object obj, int nestingLevel, HashSet visited, MemberInfo member, Type expectedType) { if (obj == null) - return $"null ({expectedType.Name}){Environment.NewLine}"; + { + sb.Append("null ("); + sb.Append(expectedType.Name); + sb.Append(')'); + sb.Append(Environment.NewLine); + return; + } var type = obj.GetType(); if (member != null && _memberSerializers.TryGetValue(member, out var memberSerializer)) - return memberSerializer(obj) + Environment.NewLine; + { + sb.Append(memberSerializer(obj)); + sb.Append(Environment.NewLine); + return; + } if (_typeSerializers.TryGetValue(type, out var typeSerializer)) - return typeSerializer(obj) + Environment.NewLine; + { + sb.Append(typeSerializer(obj)); + sb.Append(Environment.NewLine); + return; + } if (IsFinalType(type)) { - var text = FormatFinalValue(obj, type, member); - return text + Environment.NewLine; + sb.Append(FormatFinalValue(obj, type, member)); + sb.Append(Environment.NewLine); + return; } if (!visited.Add(obj)) - return "cyclic reference" + Environment.NewLine; + { + sb.Append("cyclic reference"); + sb.Append(Environment.NewLine); + return; + } - var indent = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); + sb.Append(type.Name); + sb.Append(Environment.NewLine); - sb.AppendLine(type.Name); + var indent = new string('\t', nestingLevel + 1); foreach (var memberInfo in GetSerializableMembers(type)) { @@ -135,13 +158,12 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi var value = GetMemberValue(obj, memberInfo); var memberType = GetMemberType(memberInfo); - sb.Append(indent) - .Append(memberInfo.Name) - .Append(" = ") - .Append(PrintToString(value, nestingLevel + 1, visited, memberInfo, memberType)); - } + sb.Append(indent); + sb.Append(memberInfo.Name); + sb.Append(" = "); - return sb.ToString(); + AppendTo(sb, value!, nestingLevel + 1, visited, memberInfo, memberType); + } } private static Type GetMemberType(MemberInfo member) From 018463a6fa8811e733d0cc5e2e9e15a655cd4b85 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Mon, 15 Dec 2025 18:59:07 +0500 Subject: [PATCH 13/16] =?UTF-8?q?=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=BE?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 59 ++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 5a6cb9d3d..b0e9dbb92 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -18,12 +18,17 @@ public class PrintingConfig : IPrintingConfig private readonly Dictionary _memberStringTrims = []; private readonly Dictionary _typeStringTrims = []; + private readonly object _sync = new(); + internal void SetTypeSerializer(Type type, Func serializer) { ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(serializer); - _typeSerializers[type] = serializer; + lock (_sync) + { + _typeSerializers[type] = serializer; + } } internal void SetMemberSerializer(MemberInfo member, Func serializer) @@ -31,7 +36,10 @@ internal void SetMemberSerializer(MemberInfo member, Func serial ArgumentNullException.ThrowIfNull(member); ArgumentNullException.ThrowIfNull(serializer); - _memberSerializers[member] = serializer; + lock (_sync) + { + _memberSerializers[member] = serializer; + } } internal void SetTypeCulture(Type type, CultureInfo culture) @@ -39,7 +47,10 @@ internal void SetTypeCulture(Type type, CultureInfo culture) ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(culture); - _typeCultures[type] = culture; + lock (_sync) + { + _typeCultures[type] = culture; + } } internal void SetMemberCulture(MemberInfo member, CultureInfo culture) @@ -47,25 +58,39 @@ internal void SetMemberCulture(MemberInfo member, CultureInfo culture) ArgumentNullException.ThrowIfNull(member); ArgumentNullException.ThrowIfNull(culture); - _memberCultures[member] = culture; + lock (_sync) + { + _memberCultures[member] = culture; + } } internal void SetMemberStringTrim(MemberInfo member, int maxLength) { ArgumentNullException.ThrowIfNull(member); - _memberStringTrims[member] = maxLength; + + lock (_sync) + { + _memberStringTrims[member] = maxLength; + } } internal void SetTypeStringTrim(Type type, int maxLength) { ArgumentNullException.ThrowIfNull(type); - _typeStringTrims[type] = maxLength; + + lock (_sync) + { + _typeStringTrims[type] = maxLength; + } } public IPrintingConfig Excluding() { - _excludedTypes.Add(typeof(TPropType)); - return this; + lock (_sync) + { + _excludedTypes.Add(typeof(TPropType)); + return this; + } } public IPrintingConfig Excluding(Expression> memberSelector) @@ -75,8 +100,11 @@ public IPrintingConfig Excluding(Expression> if (memberSelector.Body is not MemberExpression expression) throw new ArgumentException("Выражение должно указывать на поле или свойство.", nameof(memberSelector)); - _excludedMembers.Add(expression.Member); - return this; + lock (_sync) + { + _excludedMembers.Add(expression.Member); + return this; + } } public IPropertyPrintingConfig Printing() @@ -96,12 +124,15 @@ public IPropertyPrintingConfig Printing(Expression(new ReferenceEqualityComparer()); - var sb = new StringBuilder(); + lock (_sync) + { + var visited = new HashSet(new ReferenceEqualityComparer()); + var sb = new StringBuilder(); - AppendTo(sb, obj!, 0, visited, null!, typeof(TOwner)); + AppendTo(sb, obj!, 0, visited, null!, typeof(TOwner)); - return sb.ToString(); + return sb.ToString(); + } } private void AppendTo(StringBuilder sb, object obj, int nestingLevel, HashSet visited, MemberInfo member, Type expectedType) From de5a39afe44d888142357fb48d30c8e2bcd50ba6 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Mon, 15 Dec 2025 19:10:13 +0500 Subject: [PATCH 14/16] =?UTF-8?q?=D0=9F=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=83=20Using?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/IPropertyPrintingConfig.cs | 4 ++-- ObjectPrinting/PropertyPrintingConfig.cs | 4 ++-- ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ObjectPrinting/IPropertyPrintingConfig.cs b/ObjectPrinting/IPropertyPrintingConfig.cs index 08d5247db..4e21a473e 100644 --- a/ObjectPrinting/IPropertyPrintingConfig.cs +++ b/ObjectPrinting/IPropertyPrintingConfig.cs @@ -5,9 +5,9 @@ namespace ObjectPrinting; public interface IPropertyPrintingConfig : IPrintingConfig { - IPropertyPrintingConfig Using(Func serializer); + IPropertyPrintingConfig UsingSerializer(Func serializer); - IPropertyPrintingConfig Using(CultureInfo culture); + IPropertyPrintingConfig UsingCulture(CultureInfo culture); IPropertyPrintingConfig TrimmedToLength(int maxLength); } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index 3f4642a10..94a9a9e18 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -11,7 +11,7 @@ public class PropertyPrintingConfig(PrintingConfig parent private readonly Type _targetType = targetType; private readonly MemberInfo? _memberInfo = memberInfo; - public IPropertyPrintingConfig Using(Func serializer) + public IPropertyPrintingConfig UsingSerializer(Func serializer) { ArgumentNullException.ThrowIfNull(serializer); @@ -25,7 +25,7 @@ public IPropertyPrintingConfig Using(Func serializ return this; } - public IPropertyPrintingConfig Using(CultureInfo culture) + public IPropertyPrintingConfig UsingCulture(CultureInfo culture) { ArgumentNullException.ThrowIfNull(culture); diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 79e53631a..6b81a9c95 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -41,7 +41,7 @@ public void PrintToStringExtension_DefaultConfiguration_DefaultSerialization() [Test] public void PrintToStringExtension_WithCustomConfiguration_ApplyCustomConfiguration() { - var result = _person.PrintToString(cfg => cfg.Printing(p => p.Name).TrimmedToLength(TrimmedLength).Printing(p => p.Height).Using(InvariantCulture)); + var result = _person.PrintToString(cfg => cfg.Printing(p => p.Name).TrimmedToLength(TrimmedLength).Printing(p => p.Height).UsingCulture(InvariantCulture)); result.Should().Contain("Mi"); result.Should().Contain("1.76"); @@ -61,7 +61,7 @@ public void PrintToString_ExcludingType_SkipAllMembersOfThisType() [Test] public void PrintToString_UsingCustomSerializerForType_ApplySerializerToAllMembersOfType() { - _printer = _printer.Printing().Using(i => $"Целое число: {i}"); + _printer = _printer.Printing().UsingSerializer(i => $"Целое число: {i}"); var result = _printer.PrintToString(_person); @@ -71,7 +71,7 @@ public void PrintToString_UsingCustomSerializerForType_ApplySerializerToAllMembe [Test] public void PrintToString_SetCultureForNumericType_ApplyCultureToSerialization() { - _printer = _printer.Printing().Using(InvariantCulture); + _printer = _printer.Printing().UsingCulture(InvariantCulture); var result = _printer.PrintToString(_person); @@ -81,7 +81,7 @@ public void PrintToString_SetCultureForNumericType_ApplyCultureToSerialization() [Test] public void PrintToString_CustomSerializerForProperty_ChangeOnlyThisProperty() { - _printer = _printer.Printing(p => p.Age).Using(a => $"Возраст человека: {a}"); + _printer = _printer.Printing(p => p.Age).UsingSerializer(a => $"Возраст человека: {a}"); var result = _printer.PrintToString(_person); From 632d54c8a4d87a5a10095ed9defed6e144758b38 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Mon, 15 Dec 2025 20:12:03 +0500 Subject: [PATCH 15/16] =?UTF-8?q?=D0=BD=D0=BE=D1=80=D0=BC=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=82=D0=B8=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 43 ++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index b0e9dbb92..5d737a9af 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -25,9 +25,11 @@ internal void SetTypeSerializer(Type type, Func serializer) ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(serializer); + var normalizedType = NormalizeType(type); + lock (_sync) { - _typeSerializers[type] = serializer; + _typeSerializers[normalizedType] = serializer; } } @@ -47,9 +49,11 @@ internal void SetTypeCulture(Type type, CultureInfo culture) ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(culture); + var normalizedType = NormalizeType(type); + lock (_sync) { - _typeCultures[type] = culture; + _typeCultures[normalizedType] = culture; } } @@ -78,17 +82,21 @@ internal void SetTypeStringTrim(Type type, int maxLength) { ArgumentNullException.ThrowIfNull(type); + var normalizedType = NormalizeType(type); + lock (_sync) { - _typeStringTrims[type] = maxLength; + _typeStringTrims[normalizedType] = maxLength; } } public IPrintingConfig Excluding() { + var normalizedType = NormalizeType(typeof(TPropType)); + lock (_sync) { - _excludedTypes.Add(typeof(TPropType)); + _excludedTypes.Add(normalizedType); return this; } } @@ -109,7 +117,8 @@ public IPrintingConfig Excluding(Expression> public IPropertyPrintingConfig Printing() { - return new PropertyPrintingConfig(this, typeof(TProp), null!); + var normalizedType = NormalizeType(typeof(TProp)); + return new PropertyPrintingConfig(this, normalizedType, null!); } public IPropertyPrintingConfig Printing(Expression> memberSelector) @@ -119,7 +128,8 @@ public IPropertyPrintingConfig Printing(Expression(this, typeof(TProp), expression.Member); + var normalizedType = NormalizeType(typeof(TProp)); + return new PropertyPrintingConfig(this, normalizedType, expression.Member); } public string PrintToString(TOwner obj) @@ -146,7 +156,7 @@ private void AppendTo(StringBuilder sb, object obj, int nestingLevel, HashSet GetSerializableMembers(Type type) private static bool IsFinalType(Type type) { + type = NormalizeType(type); + return type.IsPrimitive || type == typeof(string) || type == typeof(decimal) @@ -246,6 +258,8 @@ private static bool IsFinalType(Type type) private string FormatFinalValue(object obj, Type type, MemberInfo? member) { + type = NormalizeType(type); + string result; CultureInfo? culture = null; @@ -294,6 +308,15 @@ private bool ShouldSkipMember(MemberInfo member) _ => null }; - return type != null && _excludedTypes.Contains(type); + return type != null && _excludedTypes.Contains(NormalizeType(type)); + } + + private static Type NormalizeType(Type type) + { + var underlying = Nullable.GetUnderlyingType(type); + if (underlying != null) + return underlying; + + return type; } } From e95b6474a7e1aa8f2e7b75abae12eb0c14efeaf9 Mon Sep 17 00:00:00 2001 From: Roman Gleizer Date: Wed, 17 Dec 2025 18:26:04 +0500 Subject: [PATCH 16/16] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 59 ++++++++++++++++-------- ObjectPrinting/PropertyPrintingConfig.cs | 3 ++ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 5d737a9af..c9ee0a272 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -71,6 +71,8 @@ internal void SetMemberCulture(MemberInfo member, CultureInfo culture) internal void SetMemberStringTrim(MemberInfo member, int maxLength) { ArgumentNullException.ThrowIfNull(member); + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), "Параметр maxLength должен быть неотрицательным."); lock (_sync) { @@ -81,6 +83,8 @@ internal void SetMemberStringTrim(MemberInfo member, int maxLength) internal void SetTypeStringTrim(Type type, int maxLength) { ArgumentNullException.ThrowIfNull(type); + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), "Параметр maxLength должен быть неотрицательным."); var normalizedType = NormalizeType(type); @@ -186,24 +190,31 @@ private void AppendTo(StringBuilder sb, object obj, int nestingLevel, HashSet GetSerializableMembers(Type type) private static object? GetMemberValue(object obj, MemberInfo member) { - return member switch + try { - FieldInfo field => field.GetValue(obj), - PropertyInfo property => property.GetValue(obj), - _ => null - }; + return member switch + { + FieldInfo field => field.GetValue(obj), + PropertyInfo property => property.GetValue(obj), + _ => null + }; + } + catch (TargetInvocationException ex) when (ex.InnerException != null) + { + return $""; + } + catch (Exception ex) + { + return $""; + } } private static bool IsFinalType(Type type) @@ -249,6 +271,7 @@ private static bool IsFinalType(Type type) type = NormalizeType(type); return type.IsPrimitive + || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime) diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index 94a9a9e18..8adf26354 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -39,6 +39,9 @@ public IPropertyPrintingConfig UsingCulture(CultureInfo culture) public IPropertyPrintingConfig TrimmedToLength(int maxLength) { + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), "Параметр maxLength должен быть неотрицательным."); + if (_memberInfo != null) _parent.SetMemberStringTrim(_memberInfo, maxLength); else