Skip to content

Commit

Permalink
Homework solution
Browse files Browse the repository at this point in the history
  • Loading branch information
BMV989 committed Dec 11, 2024
1 parent 9619aee commit e401825
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 60 deletions.
6 changes: 6 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ObjectPrinting;

public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj) => ObjectPrinter.For<T>().PrintToString(obj);
}
12 changes: 4 additions & 8 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
namespace ObjectPrinting
namespace ObjectPrinting;

public static class ObjectPrinter
{
public class ObjectPrinter
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
}
public static PrintingConfig<T> For<T>() => new();
}
163 changes: 139 additions & 24 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -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<TOwner>
{
public class PrintingConfig<TOwner>
private readonly HashSet<Type> excludedTypes = [];
private readonly HashSet<string> excludedProperties = [];
private readonly Dictionary<Type, Func<object, string>> typeSerializers = new();
private readonly Dictionary<Type, CultureInfo> typeCultures = new();
private readonly Dictionary<string, Func<object, string>> propertySerializers = new();

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>() => new(this);

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector) =>
new(this, memberSelector);

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> 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<TOwner> Excluding<TPropType>()
{
excludedTypes.Add(typeof(TPropType));
return this;
}

public string PrintToString(TOwner obj) => PrintToString(obj, 0);

public void AddSerializerForType<TPropType>(Func<TPropType, string> serializer) =>
typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj);

public void AddSerializerForProperty(string propertyName, Func<object, string> serializer) =>
propertySerializers[propertyName] = serializer;

public void AddCultureForType<TPropType>(CultureInfo culture) => typeCultures[typeof(TPropType)] = culture;



private string PrintToString(object? obj, int nestingLevel, HashSet<object>? 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<object> 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<object> 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<object> 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();
}
}
33 changes: 33 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Linq.Expressions;

namespace ObjectPrinting;

public class PropertyPrintingConfig<TOwner, TPropType>(PrintingConfig<TOwner> printingConfig,
Expression<Func<TOwner, TPropType>> memberSelector = null!) : IPropertyPrintingConfig<TOwner, TPropType>
{
public Expression<Func<TOwner, TPropType>> MemberSelector => memberSelector;

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
printingConfig.AddSerializerForType(print);
return printingConfig;
}

public PrintingConfig<TOwner> 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<TPropType>(culture);
return printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
}

public interface IPropertyPrintingConfig<TOwner, TPropType>
{
PrintingConfig<TOwner> ParentConfig { get; }
}
29 changes: 29 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting;

public static class PropertyPrintingConfigExtensions
{
public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config) =>
config(ObjectPrinter.For<T>()).PrintToString(obj);

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
if (maxLen < 0)
throw new ArgumentOutOfRangeException(nameof(maxLen), "Max length must be non-negative!");

IPropertyPrintingConfig<TOwner, string> 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;
}
}
49 changes: 29 additions & 20 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -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<Person>();
//1. Исключить из сериализации свойства определенного типа
//2. Указать альтернативный способ сериализации для определенного типа
//3. Для числовых типов указать культуру
//4. Настроить сериализацию конкретного свойства
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
//6. Исключить из сериализации конкретного свойства

string s1 = printer.PrintToString(person);
var printer = ObjectPrinter.For<Person>()
//1. Исключить из сериализации свойства определенного типа
.Excluding<Guid>()
//2. Указать альтернативный способ сериализации для определенного типа
.Printing<int>().Using(i => i.ToString("X"))
//3. Для числовых типов указать культуру
.Printing<double>().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));
}
}
15 changes: 7 additions & 8 deletions ObjectPrinting/Tests/Person.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
3 changes: 3 additions & 0 deletions fluent-api.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=13FAEF5AD10371439A650EAFEB60E612/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=13FAEF5AD10371439A650EAFEB60E612/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=13FAEF5AD10371439A650EAFEB60E612/Categories/=Imported_002010_002E10_002E2016/@EntryIndexedValue">Imported 10.10.2016</s:String>
Expand Down

0 comments on commit e401825

Please sign in to comment.