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

Алешев Руслан #202

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System;
using System.Text;

namespace ObjectPrinting
{
public class ObjectPrinter
Expand All @@ -7,4 +10,12 @@ public static PrintingConfig<T> For<T>()
return new PrintingConfig<T>();
}
}

public static class ObjectPrinterExtensions
{
public static string PrintToString<T>(this T obj)
{
return ObjectPrinter.For<T>().PrintToString(obj);
}
}
}
3 changes: 3 additions & 0 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
<LangVersion>8</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<StartupObject>AutoGeneratedProgram</StartupObject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
Copy link

Choose a reason for hiding this comment

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

Давай для тестов заводить отдельный проект, чтоб не мешать тесты и какую-либо логику. Плюсом в основном коде не будут доступны тестовые классы/методы (если правильно настроить область видимости), размер сборок станет меньше, проще будет запускать тесты отдельно и т.д.

Для Rider'а:
Добавляешь папку Tests: [имя твоего решения] -> клик ПКМ -> New Solution Project.
Делаешь вот так, указываешь нужное имя в Project name.
image

На выходе получается вот такое
image

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>

</Project>
122 changes: 111 additions & 11 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,139 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
private readonly HashSet<PropertyInfo> excludedProperties = new HashSet<PropertyInfo>();
private readonly HashSet<Type> excludedTypes = new HashSet<Type>();
Copy link

Choose a reason for hiding this comment

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

Думаю, можно воспользоваться короткой формой = new();. Если нет, то и нет, но можно поднять .net и версию C#

Copy link
Author

Choose a reason for hiding this comment

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

✔️


internal readonly Dictionary<Type, Func<object, string>> TypeSerializers = new Dictionary<Type, Func<object, string>>();
internal readonly Dictionary<PropertyInfo, Func<object, string>> PropertySerializers = new Dictionary<PropertyInfo, Func<object, string>>();

private readonly HashSet<object> serializedObjects = new HashSet<object>();

public PrintingConfig<TOwner> Exclude<TProp>()
{
excludedTypes.Add(typeof(TProp));
return this;
Copy link

Choose a reason for hiding this comment

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

Оставляй перед return пустую строку. И сразу проверь везде, пж)
Это делается для того, чтоб было сразу и четко ясно, что и где возвращается из метода.

Copy link
Author

Choose a reason for hiding this comment

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

✔️

}
Copy link

Choose a reason for hiding this comment

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

Перед сдачей решения запуская, пожалуйста, форматирование кода. Для Rider'a это сочетание ctrl + alt + L / shift + ctrl + alt + L. Либо можно настроить автоформатирование на сохранение кода ctrl s. Для Visual Studio не подскажу, но это гуглится. В коде оч много мест, где следовало бы применить форматирование.

public PrintingConfig<TOwner> Exclude<TProp>(Expression<Func<TOwner, TProp>> exclude)
{
var propertyInfo = ((MemberExpression)exclude.Body).Member as PropertyInfo;
excludedProperties.Add(propertyInfo);
return this;
}

public TypePrintingConfig<TOwner,TProperty> Serialize<TProperty>()
{
return new TypePrintingConfig<TOwner,TProperty>(this);
}

public PropertyPrintingConfig<TOwner, TProp> Serialize<TProp>(Expression<Func<TOwner, TProp>> customSerialize)
Copy link

Choose a reason for hiding this comment

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

А если я заведу в объекте поле, смогу ли я его преобразовать?
Кажется, что customSerialize можно дать более говорящее имя. По сути, это какой-то изменятор, я бы от этого отталкивался.

{
var propertyInfo = ((MemberExpression)customSerialize.Body).Member as PropertyInfo;
return new PropertyPrintingConfig<TOwner, TProp>(this, propertyInfo);
}

public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}

private string DictionarySerialize(PropertyInfo propertyInfo, object obj)
{
var dictionaryValue = propertyInfo.GetValue(obj) as IDictionary;
if (dictionaryValue == null)
{
return string.Empty;
}

var keys = dictionaryValue.Keys.OfType<object>().Select(item => item.ToString()).ToArray();
var values = dictionaryValue.Values.OfType<object>().Select(item => item.ToString()).ToArray();
var pair = keys.Zip(values).Select(pair => string.Format("({0}, {1})", pair.First, pair.Second));

return $"{propertyInfo.Name} = {{ " + string.Join(", ", pair) + " }";
Copy link

Choose a reason for hiding this comment

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

Тут можно использовать интерполяцию целиком, без конкатенации.

}

private string IEnumerableSerialize(PropertyInfo propertyInfo, object obj)
{
var listValue = propertyInfo.GetValue(obj) as IEnumerable;
if (listValue == null)
{
return string.Empty;
}

var items = listValue.OfType<object>().Select(item => item.ToString()).ToArray();
return $"{propertyInfo.Name} = {{ " + string.Join(", ", items) + " }";
}

private string PrintToString(object obj, int nestingLevel)
Copy link

Choose a reason for hiding this comment

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

Кажется, что реализация сериализации должна лежать не в PrintingConfig

{
//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;
if (serializedObjects.Contains(obj))
Copy link

Choose a reason for hiding this comment

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

Потенциально из-за этого места могут быть утечки памяти. Например, кто-то решит очень долго и часто использовать твой сериализатор, в итоге в этом хешсете наберется очень много объектов, которые вряд ли уже кому-то нужны.

return obj.GetType().GetTypeInfo().Name + Environment.NewLine;

serializedObjects.Add(obj);

var identation = new string('\t', nestingLevel + 1);

if(nestingLevel > 5)
return identation;
Copy link

Choose a reason for hiding this comment

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

У тебя однострочные if то со скобками, то нет. Стоит привести к единообразию везде.


var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);

if (type.IsValueType || obj is string)
{
if (TypeSerializers.ContainsKey(type))
{

return TypeSerializers[type](obj) + Environment.NewLine;
}
return obj + Environment.NewLine;
}

if (type.IsValueType && TypeSerializers.ContainsKey(type))
Copy link

Choose a reason for hiding this comment

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

Мб, вместо ContainsKey получится использовать TryGetValue? ContainsKey и обращение по ключу - TypeSerializers[type] будут дважды запускать поиск ключа.

{
sb.Append(TypeSerializers[type](obj) + Environment.NewLine);
}

foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
if (excludedProperties.Contains(propertyInfo))
continue;
if (excludedTypes.Contains(propertyInfo.PropertyType))
Copy link

Choose a reason for hiding this comment

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

Не стесняйся оставлять пустые строки между условиями.
Вообще говоря, конкретно эти два условия можно было бы объединить.

continue;

if (PropertySerializers.ContainsKey(propertyInfo))
{
sb.Append(identation + propertyInfo.Name + " = " + PropertySerializers[propertyInfo](propertyInfo.GetValue(obj)) + Environment.NewLine);
}
else if (propertyInfo.PropertyType.IsArray
Copy link

Choose a reason for hiding this comment

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

Сложные условия лучше выносить в отдельную переменную/метод, давая им понятные хорошие имена.

|| (propertyInfo.PropertyType.IsGenericType
&& propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>)))
{
sb.Append(identation + IEnumerableSerialize(propertyInfo, obj) + Environment.NewLine);
}
else if (propertyInfo.PropertyType.IsGenericType
&& propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
sb.Append(identation + DictionarySerialize(propertyInfo, obj) + Environment.NewLine);
Copy link

Choose a reason for hiding this comment

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

Внутри DictionarySerialize ты проверяешь на IDictionary без дженериков. Кажется, будто если подать на вход Dictionary без дженериков aka Hashtable, то условие не пройдет, хотя код поддерживает такой тип? Давай просто в тесты это добавим, если пройдет - круто. Не пройдет - не страшно)

}
else
{
sb.Append(identation + propertyInfo.Name + " = " + PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1));
Copy link

Choose a reason for hiding this comment

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

Давай везде использовать интерполяцию

}
}
return sb.ToString();
}
Expand Down
35 changes: 35 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using ObjectPrinting;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PropertyPrintingConfig<TOwner, TPropType> : IPropertyPrintingConfig<TOwner, TPropType>
{
public readonly PrintingConfig<TOwner> printingConfig;
Copy link

Choose a reason for hiding this comment

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

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

Но, вообще говоря, открытые поля если и есть, то должны использоваться только для чтения. readonly в данном случае не означает, что ты не можешь изменить объект printingConfig. readonly означает, что ты не можешь его перезаписать, а инициализировать такое поле можно только при объявлении, либо в конструкторе.

В общем, тебе либо не нужен public тут, либо давай всё же сделаем это свойством.

public readonly PropertyInfo propertyInfo;
PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
Copy link

Choose a reason for hiding this comment

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

А тут вовсе отсутствует модификатор доступа, т.е. свойство по умолчанию будет иметь модификатор доступа private - использоваться только внутри класса. И всё приправлено явной реализацией интерфейса. Т.е. интерфейс реализован, но до тех пор, пока мы явно не придем тип к типу интерфейса, мы не сможем обратиться к этому свойству. Зачем такие сложности?)


public PropertyPrintingConfig(PrintingConfig<TOwner> printingConfig, PropertyInfo propertyInfo)
{
this.printingConfig = printingConfig;
this.propertyInfo = propertyInfo;
}

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
var func = new Func<object, string>(obj => print((TPropType)obj));
printingConfig.PropertySerializers.Add(propertyInfo, func);
return printingConfig;
}
}
public interface IPropertyPrintingConfig<TOwner, TPropType>
Copy link

Choose a reason for hiding this comment

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

Обычно для интерфейсов выделают отдельный файл, хотя и не всегда. Как бы ни было, давай поднимем интерфейс на верх файла.

{
PrintingConfig<TOwner> ParentConfig { get; }
}
}
22 changes: 22 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public static class PropertyPrintingConfigExtensions
{
public static PrintingConfig<TOwner> Trim<TOwner>(this PropertyPrintingConfig<TOwner, string> propertyPrintingConfig, int length)
Copy link

Choose a reason for hiding this comment

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

Trim - обрезание пробелов. Если бы вместо Substring был вызван Trim - ок. Но тут мы просто обрезаем строку по длине. Так что над именем метода надо ещё подумать.

{
var cultureFunc = new Func<string, string>(s => s.Substring(0, Math.Min(length, s.Length)));
Copy link

Choose a reason for hiding this comment

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

Причем тут culture?
А если значение length отрицательное?

var func = new Func<object, string>(obj => cultureFunc((string)obj));
var prop = propertyPrintingConfig.printingConfig;
Copy link

Choose a reason for hiding this comment

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

Не очень красиво получается - идет обращение к публичному полю, из которого мы что-то достаем, у чего поменяли состояние.

Надо этот процесс сделать прозрачнее. Первое, что приходит в голову - сделать этот метод не экстеншеном, а обернуть в класс-наследник, который бы умел делать то же самое. Тогда поляхи станут уже не internal, а protected, а само изменение состояние объекта будет вполне ожидаемым.

prop.PropertySerializers.Add(propertyPrintingConfig.propertyInfo, func);

return prop;
}
}
}
13 changes: 13 additions & 0 deletions ObjectPrinting/Tests/IEnums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace ObjectPrinting.Tests
{
public class IEnums
Copy link

Choose a reason for hiding this comment

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

  1. Префикс I добавляется к имени интерфейса. У тебя класс, поэтому тут это не нужно.
  2. Enum - используется в .net. Не стоит создавать поводом для разночтения твоего кода. C Enum или enum твой класс никак не связан, поэтому и повода так думать давать не надо.
  3. Класс точно не должен быть публичным.
  4. А если сделать массивов массивов, т.е. ступенчатый массив? Или словарь словарей? Копать в эту сторону не надо, но было бы интересно увидеть это в тестах.

{
public int[] Array { get; set; }
public List<int> List { get; set; }
public Dictionary<char,int> Dict { get; set; }
}
}
113 changes: 100 additions & 13 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,114 @@
using NUnit.Framework;
using FluentAssertions;
using System;
using System.Globalization;
using System.Collections.Generic;

namespace ObjectPrinting.Tests
{
[TestFixture]
public class ObjectPrinterAcceptanceTests
Copy link

Choose a reason for hiding this comment

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

Нигде нет структуры ААА

{
Person person = new Person();

[SetUp]
Copy link

Choose a reason for hiding this comment

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

Не стоит привыкать к использованию метода SetUp. Его использование скорее редкость, нежели стандартная практика. В твоем случае ничего не мешает прописать private Person person => new() (не забывай указывать модификаторы доступа) или использовать какие-нибудь фабрики / TestDataBuilder / Object Mother.

public void Setup()
{
person = new Person { Name = "Alex", Age = 19, Height = 12.34, Id = new Guid() };
}

[Test]
public void PrintToString_SkipExcludedPropertys()
Copy link

Choose a reason for hiding this comment

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

Кажется, очепятка закралась - Propertys -> Properties.
Мб, стоило просто в члены класса запихнуть гуид, а Person инициализировать с ним? Тогда вместо нулей можно было бы писать через интерполяцию этот самый гуид.

{
var printer = ObjectPrinter.For<Person>().Exclude(x=>x.Age);
Copy link

Choose a reason for hiding this comment

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

Часто тестируемый модуль называют sut - system under test. Так проще ориентироваться в тесте.

Вообще, ещё есть cut - class under system. Пишут так крайне редко (по крайней мере, исходя из моего опыта), но для полноты картинки пусть будет.

var serialized = printer.PrintToString(person);
serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12,34\r\n\tparent = null\r\n");
Copy link

Choose a reason for hiding this comment

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

  1. У тебя уже есть эталонный экземпляр Person, почему бы не использовать его и интерполировать строку?
  2. \r\n\t выглядит немного сомнительно, особенно в конце строки.
  3. Кажется, что запись в столбик будет читабельнее, чем в строку. Чтоб не возиться с конкатенацией можешь использовать необработанные строковые литералы
  4. Опять же, копать в эту сторону не буду заставлять, но что, если в процессе рефакторинга кода я поменяю свойства местами? Твои проверки захардкожены на определенный порядок свойств. А он вообще никак не гарантирован - ни CLR не гарантирует порядок свойств, ни тем более другие программисты, которые могут поменять свойствам места, руководствуясь чувством прекрасного.
  5. А ещё программисты могут поменять имя свойства (с этим запариваться тоже не надо, но имей в виду)

}

[Test]
Copy link

Choose a reason for hiding this comment

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

Необязательно принимать к сведению, но я в атрибутах прописываю свойство TestOf, где в том числе указываю имя интересующего метода. А из имени теста убираю имя тестируемого метода.

public void PrintToString_SkipExcludedTypes()
{
var printer = ObjectPrinter.For<Person>().Exclude<int>();
var serialized = printer.PrintToString(person);
serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12,34\r\n\tparent = null\r\n");
}

[Test]
public void PrintToString_UseCustomSerializerForCertainType()
{
var printer = ObjectPrinter.For<Person>().Serialize<double>().Using(x=>x.ToString("f0"));
var serialized = printer.PrintToString(person);
serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12\r\n\tAge = 19\r\n\tparent = null\r\n");
}

[Test]
public void Demo()
public void PrintToString_UseCustomSerializerForCertainProperty()
{
var person = new Person { Name = "Alex", Age = 19 };
var printer = ObjectPrinter.For<Person>().Serialize(x=>x.Name).Using(x=>"Property Serializer");
var serialized = printer.PrintToString(person);
serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Property Serializer\r\n\tHeight = 12,34\r\n\tAge = 19\r\n\tparent = null\r\n");
}

[Test]
public void PrintToString_UseCustomCultureForCertainProperty()
{
var printer = ObjectPrinter.For<Person>().Serialize<double>().Using<Double>(CultureInfo.InvariantCulture);
Copy link

Choose a reason for hiding this comment

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

Примитивы обычно пишут с маленькой буквы. Исключением является случай, когда ты пишешь под среду CLR на нескольких языках программирования, но я слышал только, что так можно, а не что так делают. А поскольку в разных средах одни и те же типы данных могут иметь разное название, то указывается общий для CLR тип, прописанный в FCL.

Но это всё, по сути, бесполезные для нас тонкости - просто пиши double с маленькой буквы)

Copy link

Choose a reason for hiding this comment

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

Смущает имя Serialize. Оно как бы подходит в общем по смыслу, но, кажется, выглядит слишком обобщающе. Ведь, по сути, ты выбираешь какое-то поле/пропертю/тип, после чего применяешь к этому делу некий Using.

Думаю, оба метода можно назвать лучше - Serialize конкретнее, а Using чуть-чуть короче)

var serialized = printer.PrintToString(person);
serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12.34\r\n\tAge = 19\r\n\tparent = null\r\n");
Copy link

Choose a reason for hiding this comment

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

А можно ли применить культуру к всей сериализации? Т.е. сразу ко всем типам данных?

}

[Test]
public void PrintToString_TrimLimitStringLength()
{
var printer = ObjectPrinter.For<Person>().Serialize(x=>x.Name).Trim(1);
var serialized = printer.PrintToString(person);
Copy link

Choose a reason for hiding this comment

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

А вот PrintToString уже больше похоже на Serialize.

serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = A\r\n\tHeight = 12,34\r\n\tAge = 19\r\n\tparent = null\r\n");
}


[Test]
public void PrintToString_ParsinCyclingLinks()
{
var parent = new Person();
parent.Id = new Guid("00000000-0000-0000-0000-012147483648");
Copy link

Choose a reason for hiding this comment

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

Для гуида можно использовать var guid = Guid.NewGuid();. Тебе сразу вернется некий уникальный GUID, который можно сохранить и использовать.

Copy link

Choose a reason for hiding this comment

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

Почему бы не использовать инициализатор?
var parent = new Person {Id = new Guid("00000000-0000-0000-0000-012147483648")};?

person.parent = parent;
parent.parent = person;
var printer = ObjectPrinter.For<Person>();
//1. Исключить из сериализации свойства определенного типа
//2. Указать альтернативный способ сериализации для определенного типа
//3. Для числовых типов указать культуру
//4. Настроить сериализацию конкретного свойства
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
//6. Исключить из сериализации конкретного свойства

string s1 = printer.PrintToString(person);

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
//8. ...с конфигурированием
var serialized = printer.PrintToString(person);
serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12,34\r\n\tAge = 19\r\n\tparent = Person\r\n\t\tId = 00000000-0000-0000-0000-012147483648\r\n\t\tName = null\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\t\tparent = Person\r\n");
Copy link

Choose a reason for hiding this comment

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

А если циклическая цепочка длиннее, чем объект, ссылающийся сам на себя? Например
1 -> 2 -> 3 -> 1 ?

Как быть с вложенными объектами, которые не ссылаются сами на себя? Будет ли сериализован parent как надо? Как надо - это так, чтоб по сериализованному объекту его можно было бы десериализовать.

}

[Test]
public void PrintToString_ParsinArrays()
Copy link

Choose a reason for hiding this comment

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

Что значит Parsin?) Почему Arrays во множественном числе? К тестам ниже аналогичный вопрос

{
var arr = new IEnums();
arr.Array = new int[] { 1, 2, 3 };

var printer = ObjectPrinter.For<IEnums>();
var serialized = printer.PrintToString(arr);
serialized.Should().Be("IEnums\r\n\tArray = { 1, 2, 3 }\r\n\t\r\n\t\r\n");
Copy link

Choose a reason for hiding this comment

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

Какое-то избыточное кол-во спецсимволов в конце строки. Зачем они там?

}

[Test]
public void PrintToString_ParsinLists()
{
var list = new IEnums();
list.List = new List<int>() {1,2,3 };

var printer = ObjectPrinter.For<IEnums>();
var serialized = printer.PrintToString(list);
serialized.Should().Be("IEnums\r\n\t\r\n\tList = { 1, 2, 3 }\r\n\t\r\n");
Copy link

Choose a reason for hiding this comment

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

А что будет, если List, или любая другая коллекция, будет большая, например, 100_000 элементов, каждый из которых - сложный объект? Кажется, что выскочит StackOverflow.
По-хорошему, надо этот момент тоже учитывать - либо позволять извне настраивать глубину рекурсии, либо хотя бы оборачивать это дело в своё исключение, когда крашнется StackOverflow.

}

[Test]
public void PrintToString_ParsinDicts()
{
var dict = new IEnums();
dict.Dict = new Dictionary<char, int>() { {'a',1 },{'b',2 },{'c',3 } };

var printer = ObjectPrinter.For<IEnums>();
var serialized = printer.PrintToString(dict);
serialized.Should().Be("IEnums\r\n\t\r\n\t\r\n\tDict = { (a, 1), (b, 2), (c, 3) }\r\n");
}
}
}
Loading