-
Notifications
You must be signed in to change notification settings - Fork 245
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
base: master
Are you sure you want to change the base?
Галичев Артем #213
Changes from 3 commits
4a7015e
5153e7a
d8a8294
be6059d
704e243
b63e36f
a734a24
84286e2
bc5b75c
0c12bd1
8297e5f
c10123f
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,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(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="8.0.0-alpha.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. Не сильно важный коммент, но альфы в рабочее решение тащить опасненько |
||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> | ||
<PackageReference Include="NUnit" Version="4.2.2" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" /> | ||
|
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); | ||
} |
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; } = []; | ||
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 readonly Type[] primitiveTypes = | ||
[ | ||
typeof(int), | ||
typeof(double), | ||
typeof(float), | ||
typeof(string), | ||
typeof(DateTime), | ||
typeof(TimeSpan) | ||
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. Ещё забыл всякие uint, ulong, short и тд. Но есть более простой способ узнать, является ли тип простым, не перечисляя все вариации простых типов |
||
]; | ||
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) | ||
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 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); | ||
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. Смущает, что мы некоторые группы правил сериализации вот так через if-else гоняем, добавится ещё одно группировочное правило - придется залезать в этот класс и добавлять ещё одну ветку if-else Попробуй порефакторить это место, возможно вынести каждую группу правил в свой класс, наследовать их от общего интерфейса, а тут пройтись по ним в цикле; возможно ещё как-нибудь, но хочется избавиться от if-else, но сохранить чистоту кода при этом Если не получится, приходи в лс, обсудим |
||
|
||
return result == null ? | ||
PrintToString(propertyValue, nextNestingLevel, parsedObjects) : | ||
result + 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. Обычно немного другое форматирование для тернарника юзаем return result == null
? PrintToString(propertyValue, nextNestingLevel, parsedObjects)
: result + Environment.NewLine; |
||
} | ||
} | ||
} |
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; | ||
} | ||
} |
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) | ||
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 headConfig = propertyPrintingConfig.HeadConfig; | ||
|
||
headConfig.PropertiesMaxLength[propertyPrintingConfig.PropertyInfo] = maxLen; | ||
|
||
return headConfig; | ||
} | ||
} |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
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.
Хорошей практикой считается разделять основной код и тесты на него в разных проектах. В каждом проекте будут только те зависимости, что нужны конкретному типу проекта, а также они начнут быстрее собираться и выполняться
Может быть на текущем объеме это будет незаметно, но когда у тебя проект на сотни тысяч строк кода и сотни тестов на него, становится уже заметнее))