From e401825ad3ab9b7482eaba68bab0c5c79ee33372 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 11 Dec 2024 19:42:16 +0500 Subject: [PATCH] Homework solution --- ObjectPrinting/ObjectExtensions.cs | 6 + ObjectPrinting/ObjectPrinter.cs | 12 +- ObjectPrinting/PrintingConfig.cs | 163 +++++++++++++++--- ObjectPrinting/PropertyPrintingConfig.cs | 33 ++++ .../PropertyPrintingConfigExtensions.cs | 29 ++++ .../Tests/ObjectPrinterAcceptanceTests.cs | 49 +++--- ObjectPrinting/Tests/Person.cs | 15 +- fluent-api.sln.DotSettings | 3 + 8 files changed, 250 insertions(+), 60 deletions(-) create mode 100644 ObjectPrinting/ObjectExtensions.cs create mode 100644 ObjectPrinting/PropertyPrintingConfig.cs create mode 100644 ObjectPrinting/PropertyPrintingConfigExtensions.cs diff --git a/ObjectPrinting/ObjectExtensions.cs b/ObjectPrinting/ObjectExtensions.cs new file mode 100644 index 00000000..943adfd8 --- /dev/null +++ b/ObjectPrinting/ObjectExtensions.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting; + +public static class ObjectExtensions +{ + public static string PrintToString(this T obj) => ObjectPrinter.For().PrintToString(obj); +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c3..513af06c 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,6 @@ -namespace ObjectPrinting +namespace ObjectPrinting; + +public static class ObjectPrinter { - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } + public static PrintingConfig For() => new(); } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..90b7aff4 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,156 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Text; -namespace ObjectPrinting + +namespace ObjectPrinting; + +public class PrintingConfig { - public class PrintingConfig + private readonly HashSet excludedTypes = []; + private readonly HashSet excludedProperties = []; + private readonly Dictionary> typeSerializers = new(); + private readonly Dictionary typeCultures = new(); + private readonly Dictionary> propertySerializers = new(); + + public PropertyPrintingConfig Printing() => new(this); + + public PropertyPrintingConfig Printing( + Expression> memberSelector) => + new(this, memberSelector); + + public PrintingConfig Excluding(Expression> memberSelector) + { + if (memberSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException("Expression must be a member expression", nameof(memberSelector)); + excludedProperties.Add(memberExpression.Member.Name); + return this; + } + + internal PrintingConfig Excluding() + { + excludedTypes.Add(typeof(TPropType)); + return this; + } + + public string PrintToString(TOwner obj) => PrintToString(obj, 0); + + public void AddSerializerForType(Func serializer) => + typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj); + + public void AddSerializerForProperty(string propertyName, Func serializer) => + propertySerializers[propertyName] = serializer; + + public void AddCultureForType(CultureInfo culture) => typeCultures[typeof(TPropType)] = culture; + + + + private string PrintToString(object? obj, int nestingLevel, HashSet? visitedObjects = null) { - public string PrintToString(TOwner obj) + if (obj == null) + return "null" + Environment.NewLine; + + visitedObjects ??= []; + + if (!visitedObjects.Add(obj)) + return "Cyclic reference!" + Environment.NewLine; + + var finalTypes = new[] { - return PrintToString(obj, 0); + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + + var type = obj.GetType(); + + if (typeSerializers.TryGetValue(type, out var serializer)) + return serializer(obj) + Environment.NewLine; + + if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable) + return formattable.ToString(null, culture) + Environment.NewLine; + + if (finalTypes.Contains(type)) + return obj + Environment.NewLine; + + return obj switch + { + IDictionary dictionary => SerializeDictionary(dictionary, nestingLevel, visitedObjects), + IEnumerable enumerable => SerializeEnumerable(enumerable, nestingLevel, visitedObjects), + _ => SerializeObject(obj, nestingLevel, visitedObjects) + }; + } + + private string SerializeDictionary(IDictionary dictionary, int nestingLevel, HashSet visitedObjects) + { + var dictionaryType = dictionary.GetType().Name; + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine($"{dictionaryType}:"); + + foreach (DictionaryEntry entry in dictionary) + { + sb.Append(indentation + "Key = " + + PrintToString(entry.Key, nestingLevel + 1, visitedObjects)); + sb.Append(indentation + "Value = " + + PrintToString(entry.Value, nestingLevel + 1, visitedObjects)); } - private string PrintToString(object obj, int nestingLevel) + visitedObjects.Remove(dictionary); + return sb.ToString(); + } + + private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visitedObjects) + { + var collectionType = enumerable.GetType().Name; + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine($"{collectionType}:"); + + foreach (var element in enumerable) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + sb.Append(indentation + "- " + + PrintToString(element, nestingLevel + 1, visitedObjects)); + } + + visitedObjects.Remove(enumerable); + return sb.ToString(); + } + + private string SerializeObject(object obj, int nestingLevel, HashSet visitedObjects) + { + var type = obj.GetType(); + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); - var finalTypes = new[] + foreach (var propertyInfo in type.GetProperties()) + { + if (excludedTypes.Contains(propertyInfo.PropertyType) || + excludedProperties.Contains(propertyInfo.Name)) + continue; + + var propertyName = propertyInfo.Name; + var propertyValue = propertyInfo.GetValue(obj); + + if (propertySerializers.TryGetValue(propertyName, out var propertySerializer)) { - 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(indentation + propertyName + " = " + + propertySerializer(propertyValue!) + Environment.NewLine); + } + else { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + sb.Append(indentation + propertyName + " = " + + PrintToString(propertyValue, nestingLevel + 1, visitedObjects)); } - return sb.ToString(); } + + visitedObjects.Remove(obj); + return sb.ToString(); } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..3f04eaff --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig(PrintingConfig printingConfig, + Expression> memberSelector = null!) : IPropertyPrintingConfig +{ + public Expression> MemberSelector => memberSelector; + + public PrintingConfig Using(Func print) + { + printingConfig.AddSerializerForType(print); + return printingConfig; + } + + public PrintingConfig Using(CultureInfo culture) + { + if (!typeof(TPropType).IsValueType || !typeof(IFormattable).IsAssignableFrom(typeof(TPropType))) + throw new InvalidOperationException($"Can't apply culture for type: {typeof(TPropType)}"); + + printingConfig.AddCultureForType(culture); + return printingConfig; + } + + PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; +} + +public interface IPropertyPrintingConfig +{ + PrintingConfig ParentConfig { get; } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 00000000..a8734091 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public static class PropertyPrintingConfigExtensions +{ + public static string PrintToString(this T obj, Func, PrintingConfig> config) => + config(ObjectPrinter.For()).PrintToString(obj); + + public static PrintingConfig TrimmedToLength( + this PropertyPrintingConfig propConfig, int maxLen) + { + if (maxLen < 0) + throw new ArgumentOutOfRangeException(nameof(maxLen), "Max length must be non-negative!"); + + IPropertyPrintingConfig propertyConfig = propConfig; + var printingConfig = propertyConfig.ParentConfig; + var propertyName = ((MemberExpression)propConfig.MemberSelector.Body).Member.Name; + + printingConfig.AddSerializerForProperty(propertyName, value => + { + var val = value as string ?? string.Empty; + return val.Length > maxLen ? val[..maxLen] : val; + }); + + return printingConfig; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445..36e9d9bf 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,36 @@ -using NUnit.Framework; +using System; +using System.Globalization; +using NUnit.Framework; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests { - [TestFixture] - public class ObjectPrinterAcceptanceTests + [Test] + public void Demo() { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; + var person = new Person { Name = "Alex", Age = 19 }; - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); + var printer = ObjectPrinter.For() + //1. Исключить из сериализации свойства определенного типа + .Excluding() + //2. Указать альтернативный способ сериализации для определенного типа + .Printing().Using(i => i.ToString("X")) + //3. Для числовых типов указать культуру + .Printing().Using(CultureInfo.InvariantCulture) + //4. Настроить сериализацию конкретного свойства + .Printing(p => p.Name) + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .TrimmedToLength(10) + //6. Исключить из сериализации конкретного свойства + .Excluding(p => p.Age); + + var s1 = printer.PrintToString(person); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + var s2 = person.PrintToString(); + //8. ...с конфигурированием + var s3 = person.PrintToString(s => s.Excluding(p => p.Age)); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f9555955..9fab80f4 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +1,11 @@ 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; } } \ No newline at end of file diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ec..53fe49b2 100644 --- a/fluent-api.sln.DotSettings +++ b/fluent-api.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016