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

Трофимов Никита #200

Open
wants to merge 3 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
13 changes: 13 additions & 0 deletions ObjectPrinting/ISerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting
{
public interface ISerializer<TOwner>
{
string PrintToString(TOwner obj);
PropertySetting<TOwner> SelectProperty<P>(Expression<Func<TOwner, P>> properties);
ISerializer<TOwner> ChangeProperty<T>(Func<object, string> method);
ISerializer<TOwner> Exclude<T>();
}
}
2 changes: 1 addition & 1 deletion ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>8</LangVersion>
<LangVersion>latest</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
Copy link

@pakapik pakapik Dec 26, 2023

Choose a reason for hiding this comment

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

Есть ли причина, почему у тестов .net свежее, чем у основной сборки?

<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
Expand Down
137 changes: 122 additions & 15 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,147 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;


namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
public class PrintingConfig<TOwner> : ISerializer<TOwner>
{
private readonly Dictionary<string, PropertySetting<TOwner>> propertiesOptions = new();

private readonly Dictionary<string, StringSetting<TOwner>> stringOptions = new();

private readonly Dictionary<Type, Func<object, string>> optionsTypes = new();

private readonly HashSet<Type> exceptTypes = new();

private readonly Type[] types =
{
typeof(int),
typeof(double),
typeof(float),
typeof(bool),
typeof(DateTime),
typeof(TimeSpan)
};


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

private string PrintToString(object obj, int nestingLevel)
public PropertySetting<TOwner> SelectProperty<P>(Expression<Func<TOwner, P>> properties)
{
var memberExpression = (MemberExpression)properties.Body;
var propertySetting = new PropertySetting<TOwner>(this);
propertiesOptions[memberExpression.Member.Name] = propertySetting;

return propertySetting;
Copy link

Choose a reason for hiding this comment

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

Перед return обычно оставляют пустую строку. Это необязательно, конечно, но желательно.

}

public StringSetting<TOwner> SelectProperty(Expression<Func<TOwner, string>> properties)
{
var memberExpression = (MemberExpression)properties.Body;
var stringSetting = new StringSetting<TOwner>(this);
stringOptions[memberExpression.Member.Name] = stringSetting;
propertiesOptions[memberExpression.Member.Name] = stringSetting;

return stringSetting;
}

public ISerializer<TOwner> ChangeProperty<T2>(Func<object, string> method)
Copy link

Choose a reason for hiding this comment

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

Соблюдай конвенцию нейминга. Вылазят то T2, то Т, то Р.
Обычно пишут - <T1, T2>, либо <T, R> но под R подразумевается Result.
Ещё как вариант, у тебя тоже частично есть - <TOwner, TProperty>
Стоит привести к единообразию.

Проверь везде, пожалуйста.

{
optionsTypes.Add(typeof(T2), method);
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 обычно оставляют пустую строку. Это необязательно, конечно, но желательно.

Проверь везде, пожалуйста.

}

public ISerializer<TOwner> Exclude<T2>()
{
exceptTypes.Add(typeof(T2));
return this;
}

public PrintingConfig<TOwner> Exclude<TP>(Expression<Func<TOwner, TP>> properties)
{
var memberExpression = (MemberExpression)properties.Body;
var propertySetting = new PropertySetting<TOwner>(this, true);
propertiesOptions[memberExpression.Member.Name] = propertySetting;

return this;
}

private string PrintToString(object obj, int id, string name = "")
Copy link

Choose a reason for hiding this comment

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

Точно ли класс с постфиксом Config должен держать в себе реализацию сериализации?

{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;
return "null";

if (types.Contains(obj.GetType()) || id > 1)
Copy link

Choose a reason for hiding this comment

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

Так понимаю, types нужен для использования совместно с культурой.
Во-первых, в таком случае массив можно на что-то более оптимальное заменить.
Во-вторых, стоит дать более говорящее название, что это за types такие.
В-третьих, я пока не понял, почему id должен быть > 1)

{
var cultureInfo = CultureInfo.CurrentCulture;
if (propertiesOptions.TryGetValue(name, out var option))
cultureInfo = option.Culture;

return string.Format(cultureInfo, "{0}", obj);
}

var finalTypes = new[]
if (obj is string s)
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;
var maxLength = -1;
if (stringOptions.TryGetValue(name, out var option))
maxLength = option.MaxLength;
if (maxLength < 0)
return obj as string;

return s[..maxLength];
}

var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);
sb.Append(type.Name + ":");
foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
var variable = propertyInfo.GetValue(obj);
if (exceptTypes.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.

Сложные условия можно выносить в переменные / методы, давая тем говорящие названия.

propertiesOptions.ContainsKey(propertyInfo.Name) &&
propertiesOptions[propertyInfo.Name].IsExcept)
continue;
if (propertiesOptions.ContainsKey(propertyInfo.Name) &&
Copy link

Choose a reason for hiding this comment

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

Может, получиться заменить на TryGet?

propertiesOptions[propertyInfo.Name].OutputMethod != null)
{
sb.Append(propertyInfo.Name + " = " +
Copy link

Choose a reason for hiding this comment

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

Кажется, с интерполированием строк было бы чуть изящнее?

propertiesOptions[propertyInfo.Name].OutputMethod.Invoke(variable));
}
else if (optionsTypes.TryGetValue(propertyInfo.PropertyType, out var optionsType))
{
sb.Append(propertyInfo.Name + " = " +
optionsType.Invoke(variable));
}
else if (variable is IList list)
Copy link

Choose a reason for hiding this comment

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

А если потребуется сериализовать hashset? Тоже коллекция, но не IList. Вопрос к тому, что расширение поддерживаемых типов сериализации будет происходить за счет добавления if?

{
for (var i = 0; i < list.Count; i++)
sb.Append("\n" + i + " = " +
PrintToString(list[i], id + 1, propertyInfo.Name));
Copy link

@pakapik pakapik Dec 27, 2023

Choose a reason for hiding this comment

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

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

}
else if (variable is IDictionary dict)
{
foreach (var key in dict.Keys)
{
var value = dict[key];
sb.Append($"\n{key} = {PrintToString(value, id + 1, name)}");
}
}
else
{
sb.Append(propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj), id + 1, propertyInfo.Name));
}
}

return sb.ToString();
}
}
Expand Down
32 changes: 32 additions & 0 deletions ObjectPrinting/PropertySetting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Globalization;

namespace ObjectPrinting
{
public class PropertySetting<T>
{
protected internal bool IsExcept { get; }
Copy link

@pakapik pakapik Dec 27, 2023

Choose a reason for hiding this comment

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

IsExcept не самое лучшее название. Во-первых, Except не существительное. Во-вторых, Except несколько перекликается с Exception - не стоит кому-то давать повод для разночтения твоего кода. Попробуй привязаться к другому слову. Стоит повертеть на языке то, что ты делаешь с пропертей - пропускаешь, не учитываешь, игнорируешь и т.д.

protected internal Func<object, string> OutputMethod { get; private set; }
protected PrintingConfig<T> Config { get; private set; }
protected internal CultureInfo Culture { get; private set; }

public PropertySetting(PrintingConfig<T> config, bool isExcept = false)
{
IsExcept = isExcept;
Config = config;
Culture = CultureInfo.CurrentCulture;
Copy link

Choose a reason for hiding this comment

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

Давай вынесем в параметр конструктора, но значение по умолчанию оставим

}

public PrintingConfig<T> ChangeField(Func<object, string> func)
Copy link

Choose a reason for hiding this comment

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

Объясни, пж, почему в PropertySetting идет речь об изменении Field?

{
OutputMethod = func;
return Config;
}

public PrintingConfig<T> ChangeCulture(CultureInfo culture)
{
Culture = culture;
return Config;
}
}
}
18 changes: 18 additions & 0 deletions ObjectPrinting/StringSetting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace ObjectPrinting
{
public class StringSetting<T> : PropertySetting<T>
{
protected internal int MaxLength { get; private set; }

public StringSetting(PrintingConfig<T> config, bool isExcept = false) : base(config, isExcept)
{
MaxLength = -1;
Copy link

Choose a reason for hiding this comment

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

Почему настройки для строки инициализируются заведомо некорректным значением и почему бы не вынести это в саму пропертю?

}

public PrintingConfig<T> SetMaxLength(int length)
{
MaxLength = length;
return Config;
}
}
}
64 changes: 37 additions & 27 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
using NUnit.Framework;

namespace ObjectPrinting.Tests
{
[TestFixture]
public class ObjectPrinterAcceptanceTests
{
[Test]
public void Demo()
{
var person = new Person { Name = "Alex", Age = 19 };

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

string s1 = printer.PrintToString(person);

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
//8. ...с конфигурированием
}
}
}
// using System;
Copy link

Choose a reason for hiding this comment

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

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

// using System.Diagnostics.CodeAnalysis;
// using System.Globalization;
// using NUnit.Framework;
// using ObjectPrinting.Solved;
//
// namespace ObjectPrinting.Tests
// {
// [TestFixture]
// public class ObjectPrinterAcceptanceTests
// {
// [Test]
// public void Demo()
// {
// var person = new Person { Name = "Alex", Age = 19 };
//
// var printer = ObjectPrinter.For<Person>();
// printer
// //1. Исключить из сериализации свойства определенного типа
// .Exluding<int>()
// //2. Указать альтернативный способ сериализации для определенного типа
// //3. Для числовых типов указать культуру
// .Printing<double>().By(CultureInfo.CurrentCulture)
// //4. Настроить сериализацию конкретного свойства
// .Printing<int>().By(s => s.ToString())
// //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
//
// //6. Исключить из сериализации конкретного свойства
// .Exluding(p = p.Age);
//
// string s1 = printer.PrintToString(person);
//
// //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
// //8. ...с конфигурированием
// }
// }
// }
76 changes: 76 additions & 0 deletions ObjectPrintingTests/CollectionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using ObjectPrinting;

namespace ObjectPrintingTests;

public class CollectionsTests
{
private class TestDictionary
{
public Dictionary<string, int> DictionaryProperty { get; set; }
}

[Test]
public void PrintToString_WithDictionary()
{
var obj = new TestDictionary
{
DictionaryProperty = new Dictionary<string, int>
{
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
}
};

var printer = ObjectPrinter.For<TestDictionary>();
var result = printer.PrintToString(obj);

result.Should().Be("TestDictionary:" +
"\nOne = 1" +
"\nTwo = 2" +
"\nThree = 3");
}

private class TestArray
{
public int[] ArrayProperty { get; set; }
}

[Test]
public void PrintToString_WithArray()
{
var obj = new TestArray
{
ArrayProperty = new[] { 10, 20, 30 }
};
var printingConfig = new PrintingConfig<TestArray>();
var result = printingConfig.PrintToString(obj);

result.Should().Be("TestArray:" +
"\n0 = 10" +
"\n1 = 20" +
"\n2 = 30");
}

private class TestList
{
public List<string> ListProperty { get; set; }
}

[Test]
public void PrintToString_WithList()
Copy link

Choose a reason for hiding this comment

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

Осталось ещё либо в тестах для Person, либо ещё где добавить внутрь объекта пару полей/свойств и пару коллекций. Просто для полноты тестов

{
var obj = new TestList
{
ListProperty = new List<string> { "One", "Two", "Three" }
};

var printer = ObjectPrinter.For<TestList>();
var result = printer.PrintToString(obj);

result.Should().Be("TestList:" +
"\n0 = One" +
"\n1 = Two" +
"\n2 = Three");
}
}
Loading