From ecd136bc23ed0c7d83f56cf099654964d5d50312 Mon Sep 17 00:00:00 2001 From: Azkraft Date: Wed, 19 Nov 2025 15:37:16 +0500 Subject: [PATCH 1/5] Add homework --- Homework/Homework.csproj | 25 ++ Homework/IPrintingConfig.cs | 14 + Homework/IPropertyPrintingConfig.cs | 9 + Homework/LastMemberVisitor.cs | 15 ++ Homework/ObjectEqualityComparer.cs | 16 ++ Homework/ObjectExtensions.cs | 9 + Homework/ObjectPrinter.cs | 9 + Homework/PrintingConfig.cs | 172 ++++++++++++ Homework/PropertyPrintingConfig.cs | 35 +++ Homework/PropertyPrintingConfigExtensions.cs | 18 ++ Homework/Tests/ObjectPrinterTests.cs | 259 +++++++++++++++++++ Homework/Tests/Person.cs | 12 + fluent-api.sln | 13 +- 13 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 Homework/Homework.csproj create mode 100644 Homework/IPrintingConfig.cs create mode 100644 Homework/IPropertyPrintingConfig.cs create mode 100644 Homework/LastMemberVisitor.cs create mode 100644 Homework/ObjectEqualityComparer.cs create mode 100644 Homework/ObjectExtensions.cs create mode 100644 Homework/ObjectPrinter.cs create mode 100644 Homework/PrintingConfig.cs create mode 100644 Homework/PropertyPrintingConfig.cs create mode 100644 Homework/PropertyPrintingConfigExtensions.cs create mode 100644 Homework/Tests/ObjectPrinterTests.cs create mode 100644 Homework/Tests/Person.cs diff --git a/Homework/Homework.csproj b/Homework/Homework.csproj new file mode 100644 index 000000000..c1fb0acf6 --- /dev/null +++ b/Homework/Homework.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/Homework/IPrintingConfig.cs b/Homework/IPrintingConfig.cs new file mode 100644 index 000000000..0a6009b49 --- /dev/null +++ b/Homework/IPrintingConfig.cs @@ -0,0 +1,14 @@ +using System.Globalization; +using System.Reflection; + +namespace Homework; + +public interface IPrintingConfig +{ + HashSet ExcludeTypes { get; } + HashSet ExcludeMembers { get; } + Dictionary> AlternativeTypesSerialization { get; } + Dictionary> AlternativeMembersSerialization { get; } + Dictionary TypesCultureInfo { get; } + Dictionary StringsTrim { get; } +} diff --git a/Homework/IPropertyPrintingConfig.cs b/Homework/IPropertyPrintingConfig.cs new file mode 100644 index 000000000..6d1c8fd4d --- /dev/null +++ b/Homework/IPropertyPrintingConfig.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace Homework; + +public interface IPropertyPrintingConfig +{ + PrintingConfig ParentConfig { get; } + MemberInfo? MemberInfo { get; } +} diff --git a/Homework/LastMemberVisitor.cs b/Homework/LastMemberVisitor.cs new file mode 100644 index 000000000..0daaada64 --- /dev/null +++ b/Homework/LastMemberVisitor.cs @@ -0,0 +1,15 @@ +using System.Linq.Expressions; + +namespace Homework; + +public class LastMemberVisitor : ExpressionVisitor +{ + public MemberExpression? LastMemberExpression { get; private set; } + + protected override Expression VisitMember(MemberExpression node) + { + LastMemberExpression = node; + + return base.VisitMember(node); + } +} diff --git a/Homework/ObjectEqualityComparer.cs b/Homework/ObjectEqualityComparer.cs new file mode 100644 index 000000000..818aa2d4f --- /dev/null +++ b/Homework/ObjectEqualityComparer.cs @@ -0,0 +1,16 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Homework; + +public class ObjectEqualityComparer : IEqualityComparer +{ + public new bool Equals(object? x, object? y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode([DisallowNull] object obj) + { + return obj.GetHashCode(); + } +} diff --git a/Homework/ObjectExtensions.cs b/Homework/ObjectExtensions.cs new file mode 100644 index 000000000..020dfe390 --- /dev/null +++ b/Homework/ObjectExtensions.cs @@ -0,0 +1,9 @@ +namespace Homework; + +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/Homework/ObjectPrinter.cs b/Homework/ObjectPrinter.cs new file mode 100644 index 000000000..58e9519e3 --- /dev/null +++ b/Homework/ObjectPrinter.cs @@ -0,0 +1,9 @@ +namespace Homework; + +public class ObjectPrinter +{ + public static PrintingConfig For() + { + return new PrintingConfig(); + } +} \ No newline at end of file diff --git a/Homework/PrintingConfig.cs b/Homework/PrintingConfig.cs new file mode 100644 index 000000000..5dcdec7c8 --- /dev/null +++ b/Homework/PrintingConfig.cs @@ -0,0 +1,172 @@ +using System.Collections; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace Homework; + +public class PrintingConfig : IPrintingConfig +{ + public PropertyPrintingConfig Printing() + { + return new PropertyPrintingConfig(this, null); + } + + public PropertyPrintingConfig Printing(Expression> memberSelector) + { + var lambda = memberSelector as LambdaExpression; + var visitor = new LastMemberVisitor(); + visitor.Visit(lambda.Body); + var memberInfo = visitor.LastMemberExpression?.Member ?? throw new ArgumentException(); + + return new PropertyPrintingConfig(this, memberInfo); + } + + public PrintingConfig Excluding(Expression> memberSelector) + { + var lambda = memberSelector as LambdaExpression; + var visitor = new LastMemberVisitor(); + visitor.Visit(lambda.Body); + var memberInfo = visitor.LastMemberExpression?.Member ?? throw new ArgumentException(); + + ((IPrintingConfig)this).ExcludeMembers.Add(memberInfo); + + return this; + } + + public PrintingConfig Excluding() + { + ((IPrintingConfig)this).ExcludeTypes.Add(typeof(TPropType)); + + return this; + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, null, 0, new(new ObjectEqualityComparer())); + } + + private string PrintToString(object? obj, MemberInfo? memberInfo, int nestingLevel, Dictionary printedObjects) + { + if (obj == null) + return "null" + Environment.NewLine; + + var type = obj.GetType(); + if (printedObjects.TryGetValue(type, out var level)) + return $"(cycle with object {type.Name} at level {level}){Environment.NewLine}"; + + if (DoesTypeOverrideToString(type)) + return Serialize(obj, memberInfo) + Environment.NewLine; + + if (type.IsClass) + printedObjects.Add(type, nestingLevel); + + var result = obj is ICollection collection + ? PrintCollectionToString(collection, nestingLevel, printedObjects) + : PrintComplexObjectToString(obj, nestingLevel, printedObjects); + + if (type.IsClass) + printedObjects.Remove(type); + + return result; + } + + private string PrintCollectionToString(ICollection collection, int nestingLevel, Dictionary printedObjects) + { + var identation = new string('\t', nestingLevel); + var sb = new StringBuilder(); + sb.Append((nestingLevel > 0 ? Environment.NewLine + identation : "") + '[' + Environment.NewLine); + foreach (var item in collection) + sb.Append(identation + '\t' + + PrintToString( + item, + null, + nestingLevel + 1, + printedObjects)); + + sb.Append(identation + ']' + Environment.NewLine); + + return sb.ToString(); + } + + private string PrintComplexObjectToString(object obj, int nestingLevel, Dictionary printedObjects) + { + var type = obj.GetType(); + var identation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + foreach (var nestedMemberInfo in + GetPublicPropertiesAndFields(type).OrderBy(t => t.Name)) + { + if (((IPrintingConfig)this).ExcludeMembers.Contains(nestedMemberInfo)) + continue; + + var newObj = GetValueFromPropertyOrField(obj, nestedMemberInfo); + + if (newObj is not null + && ((IPrintingConfig)this).ExcludeTypes.Contains(newObj.GetType())) + continue; + + sb.Append(identation + nestedMemberInfo.Name + " = " + + PrintToString( + newObj, + nestedMemberInfo, + nestingLevel + 1, + printedObjects)); + } + + return sb.ToString(); + } + + private IEnumerable GetPublicPropertiesAndFields(Type type) + => type + .GetProperties() + .Select(t => (MemberInfo)t) + .Concat(type.GetFields()); + + private object? GetValueFromPropertyOrField(object obj, MemberInfo memberInfo) + { + if (memberInfo is FieldInfo fieldInfo) + return fieldInfo.GetValue(obj); + + return ((PropertyInfo)memberInfo).GetValue(obj); + } + + private string Serialize(object obj, MemberInfo? memberInfo) + { + if (memberInfo is not null + && ((IPrintingConfig)this).AlternativeMembersSerialization.TryGetValue(memberInfo, out var serializeMember)) + return serializeMember(obj); + + if (((IPrintingConfig)this).AlternativeTypesSerialization.TryGetValue(obj.GetType(), out var serializeType)) + return serializeType(obj); + + string? str; + if (((IPrintingConfig)this).TypesCultureInfo.TryGetValue(obj.GetType(), out var culture)) + str = (string)obj.GetType().GetMethod("ToString", [typeof(IFormatProvider)]).Invoke(obj, [culture]); + else + str = obj.ToString(); + + if (memberInfo is not null + && ((IPrintingConfig)this).StringsTrim.TryGetValue(memberInfo, out var maxLength) + && str?.Length > maxLength) + str = str[..maxLength]; + + return str ?? "null"; + } + + private bool DoesTypeOverrideToString(Type type) + { + var toStringMethod = type.GetMethod("ToString", Type.EmptyTypes); + return toStringMethod?.DeclaringType != typeof(object) && + toStringMethod?.DeclaringType == type; + } + + HashSet IPrintingConfig.ExcludeTypes { get; } = []; + HashSet IPrintingConfig.ExcludeMembers { get; } = []; + Dictionary> IPrintingConfig.AlternativeTypesSerialization { get; } = []; + Dictionary> IPrintingConfig.AlternativeMembersSerialization { get; } = []; + Dictionary IPrintingConfig.TypesCultureInfo { get; } = []; + Dictionary IPrintingConfig.StringsTrim { get; } = []; +} diff --git a/Homework/PropertyPrintingConfig.cs b/Homework/PropertyPrintingConfig.cs new file mode 100644 index 000000000..c9c9a5330 --- /dev/null +++ b/Homework/PropertyPrintingConfig.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using System.Reflection; + +namespace Homework; + +public class PropertyPrintingConfig(PrintingConfig printingConfig, MemberInfo? memberInfo) + : IPropertyPrintingConfig +{ + public PrintingConfig Using(Func print) + { + if (memberInfo is not null) + { + ((IPrintingConfig)printingConfig).AlternativeMembersSerialization[memberInfo] = obj => print((TPropType)obj); + } + else + { + ((IPrintingConfig)printingConfig).AlternativeTypesSerialization[typeof(TPropType)] = obj => print((TPropType)obj); + } + + return printingConfig; + } + + public PrintingConfig Using(CultureInfo culture) + { + if (typeof(TPropType).GetMethod("ToString", [typeof(IFormatProvider)]) is null) + throw new Exception(); + + ((IPrintingConfig)printingConfig).TypesCultureInfo[typeof(TPropType)] = culture; + + return printingConfig; + } + + PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; + MemberInfo? IPropertyPrintingConfig.MemberInfo => memberInfo; +} diff --git a/Homework/PropertyPrintingConfigExtensions.cs b/Homework/PropertyPrintingConfigExtensions.cs new file mode 100644 index 000000000..8d7b0bf4a --- /dev/null +++ b/Homework/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,18 @@ +namespace Homework; + +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) + { + var configIface = (IPropertyPrintingConfig)propConfig; + if (configIface.MemberInfo is not null) + ((IPrintingConfig)configIface.ParentConfig).StringsTrim[configIface.MemberInfo] = maxLen; + + return configIface.ParentConfig; + } +} \ No newline at end of file diff --git a/Homework/Tests/ObjectPrinterTests.cs b/Homework/Tests/ObjectPrinterTests.cs new file mode 100644 index 000000000..4654db194 --- /dev/null +++ b/Homework/Tests/ObjectPrinterTests.cs @@ -0,0 +1,259 @@ +using FluentAssertions; +using System.Globalization; + +namespace Homework.Tests; + +[TestFixture] +public class ObjectPrinterTests +{ + [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); + } + + [Test] + public void Should_Serialize() + { + var person = CreatePerson(); + var printer = ObjectPrinter.For(); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Parent = {person.Parent?.ToString() ?? "null"} + {"\t"}Weight = {person.Weight} + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_ExcludePropertyOrFieldWithParticularType() + { + var person = CreatePerson(); + var printer = ObjectPrinter.For() + .Excluding(); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Parent = {person.Parent?.ToString() ?? "null"} + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_UsingAlternativeSerializationForParticularType() + { + var person = CreatePerson(); + var printer = ObjectPrinter.For() + .Printing().Using(t => $"{t} y.o."); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} y.o. + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Parent = {person.Parent?.ToString() ?? "null"} + {"\t"}Weight = {person.Weight} y.o. + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_UsingCultureInfo() + { + var person = CreatePerson(); + var culture = CultureInfo.GetCultureInfo("ru-RU"); + var printer = ObjectPrinter.For() + .Printing().Using(culture); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} + {"\t"}Height = {person.Height.ToString(culture)} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Parent = {person.Parent?.ToString() ?? "null"} + {"\t"}Weight = {person.Weight} + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_ConfigurePropertyOrField() + { + var person = CreatePerson(); + var printer = ObjectPrinter.For() + .Printing(t => t.Age).Using(t => $"{t} y.o.") + .Printing(t => t.Weight).Using(t => $"{t} kg"); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} y.o. + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Parent = {person.Parent?.ToString() ?? "null"} + {"\t"}Weight = {person.Weight} kg + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_TrimString() + { + var person = CreatePerson(); + var printer = ObjectPrinter.For() + .Printing(t => t.Name).TrimmedToLength(3); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name[..3]} + {"\t"}Parent = {person.Parent?.ToString() ?? "null"} + {"\t"}Weight = {person.Weight} + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_ExcludeParticularPropertyOrField() + { + var person = CreatePerson(); + var printer = ObjectPrinter.For() + .Excluding(t => t.Parent); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Weight = {person.Weight} + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_HandlingCircularReferences() + { + var person = CreatePerson(); + person.Parent = person; + var printer = ObjectPrinter.For(); + + var actual = printer.PrintToString(person); + var expected = + $""" + Person + {"\t"}Age = {person.Age} + {"\t"}Height = {person.Height} + {"\t"}Id = {person.Id} + {"\t"}Name = {person.Name} + {"\t"}Parent = (cycle with object Person at level 0) + {"\t"}Weight = {person.Weight} + + """; + + actual.Should().Be(expected); + } + + [Test] + public void Should_SerializeCollection() + { + var dictionary = new Dictionary + { + [1] = 2, + [3] = 4, + [5] = 6 + }; + var printer = ObjectPrinter.For>(); + + var actual = printer.PrintToString(dictionary); + var expected = + $""" + [ + {"\t"}[1, 2] + {"\t"}[3, 4] + {"\t"}[5, 6] + ] + + """; + + actual.Should().Be(expected); + } + + private Person CreatePerson() + { + var person = new Person + { + Id = Guid.NewGuid(), + Age = 19, + Height = 172.5, + Name = "Name", + Weight = 75 + }; + + return person; + } +} \ No newline at end of file diff --git a/Homework/Tests/Person.cs b/Homework/Tests/Person.cs new file mode 100644 index 000000000..d83148b3a --- /dev/null +++ b/Homework/Tests/Person.cs @@ -0,0 +1,12 @@ +namespace Homework.Tests; + +public class Person +{ + public Guid Id { get; set; } + public string? Name { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public Person? Parent { get; set; } + + public int Weight; +} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9ed..22a6399cd 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36705.20 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}" EndProject @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Homework", "Homework\Homework.csproj", "{D3D88E59-B917-49B7-A7B6-78A16FCAD329}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -44,4 +50,7 @@ Global {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} {EFA9335C-411B-4597-B0B6-5438D1AE04C3} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EA57358B-B582-4077-B608-1C1F166CC04A} + EndGlobalSection EndGlobal From 7d051c0506396931e9a817ce608fac02ced3a421 Mon Sep 17 00:00:00 2001 From: Azkraft Date: Fri, 21 Nov 2025 18:13:50 +0500 Subject: [PATCH 2/5] fix: separating tests --- Homework/Homework.csproj | 18 ++---------- HomeworkTests/HomeworkTests.csproj | 29 +++++++++++++++++++ .../ObjectPrinterTests.cs | 4 ++- {Homework/Tests => HomeworkTests}/Person.cs | 2 +- fluent-api.sln | 8 ++++- 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 HomeworkTests/HomeworkTests.csproj rename {Homework/Tests => HomeworkTests}/ObjectPrinterTests.cs (98%) rename {Homework/Tests => HomeworkTests}/Person.cs (88%) diff --git a/Homework/Homework.csproj b/Homework/Homework.csproj index c1fb0acf6..bc4bb846d 100644 --- a/Homework/Homework.csproj +++ b/Homework/Homework.csproj @@ -1,25 +1,11 @@ - + net8.0 enable enable - false - true + Library - - - - - - - - - - - - - diff --git a/HomeworkTests/HomeworkTests.csproj b/HomeworkTests/HomeworkTests.csproj new file mode 100644 index 000000000..fdfda772c --- /dev/null +++ b/HomeworkTests/HomeworkTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/Homework/Tests/ObjectPrinterTests.cs b/HomeworkTests/ObjectPrinterTests.cs similarity index 98% rename from Homework/Tests/ObjectPrinterTests.cs rename to HomeworkTests/ObjectPrinterTests.cs index 4654db194..99734bf20 100644 --- a/Homework/Tests/ObjectPrinterTests.cs +++ b/HomeworkTests/ObjectPrinterTests.cs @@ -1,7 +1,8 @@ using FluentAssertions; +using Homework; using System.Globalization; -namespace Homework.Tests; +namespace HomeworkTests; [TestFixture] public class ObjectPrinterTests @@ -19,6 +20,7 @@ public void Demo() //3. Для числовых типов указать культуру .Printing().Using(CultureInfo.InvariantCulture) //4. Настроить сериализацию конкретного свойства + .Printing(p => p.Weight).Using(t => $"{t} kg") //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) .Printing(p => p.Name).TrimmedToLength(10) //6. Исключить из сериализации конкретного свойства diff --git a/Homework/Tests/Person.cs b/HomeworkTests/Person.cs similarity index 88% rename from Homework/Tests/Person.cs rename to HomeworkTests/Person.cs index d83148b3a..84d0a95d2 100644 --- a/Homework/Tests/Person.cs +++ b/HomeworkTests/Person.cs @@ -1,4 +1,4 @@ -namespace Homework.Tests; +namespace HomeworkTests; public class Person { diff --git a/fluent-api.sln b/fluent-api.sln index 22a6399cd..ff2fd98a3 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36705.20 d17.14 +VisualStudioVersion = 17.14.36705.20 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}" EndProject @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Specta EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Homework", "Homework\Homework.csproj", "{D3D88E59-B917-49B7-A7B6-78A16FCAD329}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeworkTests", "HomeworkTests\HomeworkTests.csproj", "{6EFE1C93-15A6-4405-A832-D61153373FBF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,10 @@ Global {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3D88E59-B917-49B7-A7B6-78A16FCAD329}.Release|Any CPU.Build.0 = Release|Any CPU + {6EFE1C93-15A6-4405-A832-D61153373FBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EFE1C93-15A6-4405-A832-D61153373FBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EFE1C93-15A6-4405-A832-D61153373FBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EFE1C93-15A6-4405-A832-D61153373FBF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 8fe1779b0d73f2b3a9c5c6dcdaa002efed313eeb Mon Sep 17 00:00:00 2001 From: Azkraft Date: Fri, 21 Nov 2025 18:19:44 +0500 Subject: [PATCH 3/5] fix(ObjectEqualityComparer): renamed --- ...ctEqualityComparer.cs => ObjectReferenceEqualityComparer.cs} | 2 +- Homework/PrintingConfig.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Homework/{ObjectEqualityComparer.cs => ObjectReferenceEqualityComparer.cs} (78%) diff --git a/Homework/ObjectEqualityComparer.cs b/Homework/ObjectReferenceEqualityComparer.cs similarity index 78% rename from Homework/ObjectEqualityComparer.cs rename to Homework/ObjectReferenceEqualityComparer.cs index 818aa2d4f..32726cd5f 100644 --- a/Homework/ObjectEqualityComparer.cs +++ b/Homework/ObjectReferenceEqualityComparer.cs @@ -2,7 +2,7 @@ namespace Homework; -public class ObjectEqualityComparer : IEqualityComparer +public class ObjectReferenceEqualityComparer : IEqualityComparer { public new bool Equals(object? x, object? y) { diff --git a/Homework/PrintingConfig.cs b/Homework/PrintingConfig.cs index 5dcdec7c8..96d925e31 100644 --- a/Homework/PrintingConfig.cs +++ b/Homework/PrintingConfig.cs @@ -44,7 +44,7 @@ public PrintingConfig Excluding() public string PrintToString(TOwner obj) { - return PrintToString(obj, null, 0, new(new ObjectEqualityComparer())); + return PrintToString(obj, null, 0, new(new ObjectReferenceEqualityComparer())); } private string PrintToString(object? obj, MemberInfo? memberInfo, int nestingLevel, Dictionary printedObjects) From 66bc7e3cfead695f9790e4c5ff94795a9066531a Mon Sep 17 00:00:00 2001 From: Azkraft Date: Fri, 21 Nov 2025 18:21:47 +0500 Subject: [PATCH 4/5] fix: Using with CultureInfo --- Homework/PropertyPrintingConfig.cs | 13 +------------ Homework/PropertyPrintingConfigExtensions.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Homework/PropertyPrintingConfig.cs b/Homework/PropertyPrintingConfig.cs index c9c9a5330..ee3965bfe 100644 --- a/Homework/PropertyPrintingConfig.cs +++ b/Homework/PropertyPrintingConfig.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using System.Reflection; +using System.Reflection; namespace Homework; @@ -20,16 +19,6 @@ public PrintingConfig Using(Func print) return printingConfig; } - public PrintingConfig Using(CultureInfo culture) - { - if (typeof(TPropType).GetMethod("ToString", [typeof(IFormatProvider)]) is null) - throw new Exception(); - - ((IPrintingConfig)printingConfig).TypesCultureInfo[typeof(TPropType)] = culture; - - return printingConfig; - } - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; MemberInfo? IPropertyPrintingConfig.MemberInfo => memberInfo; } diff --git a/Homework/PropertyPrintingConfigExtensions.cs b/Homework/PropertyPrintingConfigExtensions.cs index 8d7b0bf4a..bff6ac847 100644 --- a/Homework/PropertyPrintingConfigExtensions.cs +++ b/Homework/PropertyPrintingConfigExtensions.cs @@ -1,3 +1,5 @@ +using System.Globalization; + namespace Homework; public static class PropertyPrintingConfigExtensions @@ -15,4 +17,15 @@ public static PrintingConfig TrimmedToLength(this PropertyPrinti return configIface.ParentConfig; } + + public static PrintingConfig Using( + this PropertyPrintingConfig propConfig, + CultureInfo culture) + where TPropType : IFormattable + { + var configIface = (IPropertyPrintingConfig)propConfig; + ((IPrintingConfig)configIface.ParentConfig).TypesCultureInfo[typeof(TPropType)] = culture; + + return configIface.ParentConfig; + } } \ No newline at end of file From e86314adff89ea94492885e9ad751049aa96589c Mon Sep 17 00:00:00 2001 From: Azkraft Date: Sat, 22 Nov 2025 17:41:37 +0500 Subject: [PATCH 5/5] fix(PrintingConfig): nested printing --- Homework/PrintingConfig.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Homework/PrintingConfig.cs b/Homework/PrintingConfig.cs index 96d925e31..ee2342888 100644 --- a/Homework/PrintingConfig.cs +++ b/Homework/PrintingConfig.cs @@ -53,21 +53,21 @@ private string PrintToString(object? obj, MemberInfo? memberInfo, int nestingLev return "null" + Environment.NewLine; var type = obj.GetType(); - if (printedObjects.TryGetValue(type, out var level)) + if (printedObjects.TryGetValue(obj, out var level)) return $"(cycle with object {type.Name} at level {level}){Environment.NewLine}"; if (DoesTypeOverrideToString(type)) return Serialize(obj, memberInfo) + Environment.NewLine; if (type.IsClass) - printedObjects.Add(type, nestingLevel); + printedObjects.Add(obj, nestingLevel); var result = obj is ICollection collection ? PrintCollectionToString(collection, nestingLevel, printedObjects) : PrintComplexObjectToString(obj, nestingLevel, printedObjects); if (type.IsClass) - printedObjects.Remove(type); + printedObjects.Remove(obj); return result; }