Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Галичев Артем #213

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
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();
}
1 change: 1 addition & 0 deletions ObjectPrinting/ObjectPrinting.csproj

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хорошей практикой считается разделять основной код и тесты на него в разных проектах. В каждом проекте будут только те зависимости, что нужны конкретному типу проекта, а также они начнут быстрее собираться и выполняться

Может быть на текущем объеме это будет незаметно, но когда у тебя проект на сотни тысяч строк кода и сотни тестов на него, становится уже заметнее))

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.0.0-alpha.1" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не сильно важный коммент, но альфы в рабочее решение тащить опасненько

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
Expand Down
12 changes: 12 additions & 0 deletions ObjectPrinting/ObjectSerializingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace ObjectPrinting;

public static class ObjectSerializingExtensions
{
public static string PrintToString<T>(this T obj) =>
ObjectPrinter.For<T>().PrintToString(obj);

public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> printConfig) =>
printConfig(ObjectPrinter.For<T>()).PrintToString(obj);
}
148 changes: 135 additions & 13 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,163 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
public string PrintToString(TOwner obj)
public Dictionary<Type, Func<object, string>> TypeSerializers { get; } = [];
public Dictionary<Type, CultureInfo> Cultures { get; } = [];
public Dictionary<PropertyInfo, Func<object, string>> PropertySerializers { get; } = [];
public Dictionary<PropertyInfo, int> PropertiesMaxLength { get; } = [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай закроем взаимодействие с этими свойствами хотя бы в рамках сборки, чтобы при ручном создании этого конфига нельзя было вмешиваться в работу программы руками меняя эти коллекции


private readonly Type[] primitiveTypes =
[
typeof(int),
typeof(double),
typeof(float),
typeof(string),
typeof(DateTime),
typeof(TimeSpan)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ещё забыл всякие uint, ulong, short и тд.

Но есть более простой способ узнать, является ли тип простым, не перечисляя все вариации простых типов
Небольшая подсказка: посмотри в сторону класса Type

];
private readonly HashSet<Type> excludingTypes = [];
private readonly HashSet<PropertyInfo> excludingProperties = [];

public PrintingConfig<TOwner> Excluding<TPropertyType>()
{
excludingTypes.Add(typeof(TPropertyType));

return this;
}

public PrintingConfig<TOwner> Excluding<TProperty>(Expression<Func<TOwner, TProperty>> memberSelector)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сейчас у тебя возможно исключить только свойства класса, а на полях сериализация ломается. Попробуй в классе PersonWithParent сделать Id полем - тесты отвалятся, хотя поведение должно быть то же

{
return PrintToString(obj, 0);
excludingProperties.Add(GetProperty(memberSelector));

return this;
}

private string PrintToString(object obj, int nestingLevel)
public TypePrintingConfig<TOwner, TPropertyType> For<TPropertyType>() => new(this);

public PropertyPrintingConfig<TOwner, TProperty> For<TProperty>(Expression<Func<TOwner, TProperty>> memberSelector) =>
new(this, GetProperty(memberSelector));

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

private string PrintToString(object obj, int nestingLevel, Dictionary<object, int> parsedObjects)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var type = obj.GetType();

if (primitiveTypes.Contains(type))
return obj + Environment.NewLine;

if (parsedObjects.TryGetValue(obj, out var level))
return $"cycled {type.Name} in level {level}" + Environment.NewLine;
parsedObjects.Add(obj, nestingLevel);

var finalTypes = new[]
return obj switch
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
IDictionary dictionary => PrintDictionary(dictionary, nestingLevel, parsedObjects),
IEnumerable collection => PrintCollection(collection, nestingLevel, parsedObjects),
_ => PrintClassProperties(obj, nestingLevel, parsedObjects)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;
}

private string PrintDictionary(IDictionary dictionary, int nestingLevel, Dictionary<object,int> parsedObjects)
{
var sb = new StringBuilder();
var nextNestingLevel = nestingLevel + 1;
var identation = new string('\t', nextNestingLevel);
sb.AppendLine("Dictionary");

foreach (DictionaryEntry kvp in dictionary)
{
var key = kvp.Key;
var value = kvp.Value!;

sb.Append(identation + PrintToString(key, nestingLevel, parsedObjects).Trim() +
" : " +
PrintToString(value, nestingLevel, parsedObjects));
}

return sb.ToString();
}

var identation = new string('\t', nestingLevel + 1);
private string PrintCollection(IEnumerable collection, int nestingLevel, Dictionary<object,int> parsedObjects)
{
var sb = new StringBuilder();
var nextNestingLevel = nestingLevel + 1;
var identation = new string('\t', nextNestingLevel);

sb.AppendLine("Collection");
foreach (var element in collection)
sb.Append(identation + PrintToString(element, nextNestingLevel, parsedObjects));

return sb.ToString();
}

private string PrintClassProperties(object obj, int nestingLevel, Dictionary<object, int> parsedObjects)
{
var type = obj.GetType();
var sb = new StringBuilder();
var nextNestingLevel = nestingLevel + 1;
var identation = new string('\t', nextNestingLevel);

sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
if (excludingProperties.Contains(propertyInfo) || excludingTypes.Contains(propertyInfo.PropertyType))
continue;
sb.Append(identation +
propertyInfo.Name +
" = " +
PrintProperty(obj, propertyInfo, nextNestingLevel, parsedObjects));
}

return sb.ToString();
}

private PropertyInfo GetProperty<TProperty>(Expression<Func<TOwner, TProperty>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberExpression)
throw new ArgumentException($"Expression refers to a method, not a property.");

return (memberExpression.Member as PropertyInfo)!;
}

private string PrintProperty(
object obj,
PropertyInfo propertyInfo,
int nextNestingLevel,
Dictionary<object, int> parsedObjects)
{
string? result = null;
var propertyValue = propertyInfo.GetValue(obj)!;

if (PropertySerializers.TryGetValue(propertyInfo, out var propertySerializer))
result = propertySerializer(propertyValue);
else if (TypeSerializers.TryGetValue(propertyInfo.PropertyType, out var typeSerializer))
result = typeSerializer(propertyValue);
else if (PropertiesMaxLength.TryGetValue(propertyInfo, out var maxLength))
{
var propertyString = (propertyValue as string)!;
result = propertyString[..Math.Min(propertyString.Length, maxLength)];
}
else if (Cultures.TryGetValue(propertyInfo.PropertyType, out var culture))
result = string.Format(culture, "{0}", propertyValue);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Смущает, что мы некоторые группы правил сериализации вот так через if-else гоняем, добавится ещё одно группировочное правило - придется залезать в этот класс и добавлять ещё одну ветку if-else

Попробуй порефакторить это место, возможно вынести каждую группу правил в свой класс, наследовать их от общего интерфейса, а тут пройтись по ним в цикле; возможно ещё как-нибудь, но хочется избавиться от if-else, но сохранить чистоту кода при этом

Если не получится, приходи в лс, обсудим


return result == null ?
PrintToString(propertyValue, nextNestingLevel, parsedObjects) :
result + Environment.NewLine;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обычно немного другое форматирование для тернарника юзаем

return result == null 
        ? PrintToString(propertyValue, nextNestingLevel, parsedObjects) 
        : result + Environment.NewLine;

}
}
}
17 changes: 17 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Reflection;

namespace ObjectPrinting;

public class PropertyPrintingConfig<TOwner, TProperty>(PrintingConfig<TOwner> headConfig, PropertyInfo propertyInfo)
{
public PrintingConfig<TOwner> HeadConfig => headConfig;
public PropertyInfo PropertyInfo => propertyInfo;

public PrintingConfig<TOwner> Using(Func<TProperty, string> printing)
{
HeadConfig.PropertySerializers[propertyInfo] = p => printing((TProperty)p);

return HeadConfig;
}
}
15 changes: 15 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace ObjectPrinting;

public static class PropertyPrintingConfigExtensions
{
public static PrintingConfig<TOwner> MaxLength<TOwner>(
this PropertyPrintingConfig<TOwner, string> propertyPrintingConfig,
int maxLen)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай уж допишем maxLength))

{
var headConfig = propertyPrintingConfig.HeadConfig;

headConfig.PropertiesMaxLength[propertyPrintingConfig.PropertyInfo] = maxLen;

return headConfig;
}
}
10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectExtensions.cs

This file was deleted.

10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectPrinter.cs

This file was deleted.

62 changes: 0 additions & 62 deletions ObjectPrinting/Solved/PrintingConfig.cs

This file was deleted.

32 changes: 0 additions & 32 deletions ObjectPrinting/Solved/PropertyPrintingConfig.cs

This file was deleted.

18 changes: 0 additions & 18 deletions ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs

This file was deleted.

Loading