-
Notifications
You must be signed in to change notification settings - Fork 213
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
base: master
Are you sure you want to change the base?
Алешев Руслан #202
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Думаю, можно воспользоваться короткой формой There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Оставляй перед There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Перед сдачей решения запуская, пожалуйста, форматирование кода. Для |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А если я заведу в объекте поле, смогу ли я его преобразовать? |
||
{ | ||
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) + " }"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Кажется, что реализация сериализации должна лежать не в |
||
{ | ||
//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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. У тебя однострочные |
||
|
||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Мб, вместо |
||
{ | ||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Внутри |
||
} | ||
else | ||
{ | ||
sb.Append(identation + propertyInfo.Name + " = " + PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Давай везде использовать интерполяцию |
||
} | ||
} | ||
return sb.ToString(); | ||
} | ||
|
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Если ты уверен, что поле должно быть публичным, то конвенция нейминга должна соблюдаться - а поле именоваться с заглавной буквы. В конечном счете все будет зависеть от команды, в которой ты будешь, т.к. там могут быть свои правила, но они всё равно будут похожи на приведенные по ссылке. Но, вообще говоря, открытые поля если и есть, то должны использоваться только для чтения. В общем, тебе либо не нужен |
||
public readonly PropertyInfo propertyInfo; | ||
PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А тут вовсе отсутствует модификатор доступа, т.е. свойство по умолчанию будет иметь модификатор доступа |
||
|
||
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Обычно для интерфейсов выделают отдельный файл, хотя и не всегда. Как бы ни было, давай поднимем интерфейс на верх файла. |
||
{ | ||
PrintingConfig<TOwner> ParentConfig { get; } | ||
} | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
var cultureFunc = new Func<string, string>(s => s.Substring(0, Math.Min(length, s.Length))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Причем тут |
||
var func = new Func<object, string>(obj => cultureFunc((string)obj)); | ||
var prop = propertyPrintingConfig.printingConfig; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Не очень красиво получается - идет обращение к публичному полю, из которого мы что-то достаем, у чего поменяли состояние. Надо этот процесс сделать прозрачнее. Первое, что приходит в голову - сделать этот метод не экстеншеном, а обернуть в класс-наследник, который бы умел делать то же самое. Тогда поляхи станут уже не |
||
prop.PropertySerializers.Add(propertyPrintingConfig.propertyInfo, func); | ||
|
||
return prop; | ||
} | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
public int[] Array { get; set; } | ||
public List<int> List { get; set; } | ||
public Dictionary<char,int> Dict { get; set; } | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Нигде нет структуры ААА |
||
{ | ||
Person person = new Person(); | ||
|
||
[SetUp] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Не стоит привыкать к использованию метода SetUp. Его использование скорее редкость, нежели стандартная практика. В твоем случае ничего не мешает прописать |
||
public void Setup() | ||
{ | ||
person = new Person { Name = "Alex", Age = 19, Height = 12.34, Id = new Guid() }; | ||
} | ||
|
||
[Test] | ||
public void PrintToString_SkipExcludedPropertys() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Кажется, очепятка закралась - |
||
{ | ||
var printer = ObjectPrinter.For<Person>().Exclude(x=>x.Age); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Часто тестируемый модуль называют Вообще, ещё есть |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
[Test] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Необязательно принимать к сведению, но я в атрибутах прописываю свойство |
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Примитивы обычно пишут с маленькой буквы. Исключением является случай, когда ты пишешь под среду CLR на нескольких языках программирования, но я слышал только, что так можно, а не что так делают. А поскольку в разных средах одни и те же типы данных могут иметь разное название, то указывается общий для CLR тип, прописанный в FCL. Но это всё, по сути, бесполезные для нас тонкости - просто пиши There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Смущает имя Думаю, оба метода можно назвать лучше - |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А вот |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Для гуида можно использовать There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему бы не использовать инициализатор? |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А если циклическая цепочка длиннее, чем объект, ссылающийся сам на себя? Например Как быть с вложенными объектами, которые не ссылаются сами на себя? Будет ли сериализован |
||
} | ||
|
||
[Test] | ||
public void PrintToString_ParsinArrays() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Что значит |
||
{ | ||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А что будет, если |
||
} | ||
|
||
[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"); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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
.На выходе получается вот такое