-
Notifications
You must be signed in to change notification settings - Fork 244
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
Мажирин Александр #231
base: master
Are you sure you want to change the base?
Мажирин Александр #231
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 |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System; | ||
|
||
namespace ObjectPrinting.Extensions; | ||
|
||
public static class MemberPrintingConfigExtensions | ||
{ | ||
public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, IPrintingConfig<T>> config) | ||
{ | ||
return config(ObjectPrinter.For<T>()).PrintToString(obj); | ||
} | ||
|
||
public static IPrintingConfig<TOwner> TrimmedToLength<TOwner>(this MemberPrintingConfig<TOwner, string> propConfig, | ||
int maxLen) | ||
{ | ||
return ((IMemberPrintingConfig<TOwner, string>)propConfig).Config; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
namespace ObjectPrinting.Solved | ||
namespace ObjectPrinting.Extensions | ||
{ | ||
public static class ObjectExtensions | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System; | ||
using System.Linq.Expressions; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public interface IMemberPrintingConfig<TOwner, out TFieldType> | ||
{ | ||
IPrintingConfig<TOwner> Config { get; } | ||
|
||
IPrintingConfig<TOwner> With(Func<TFieldType, string> serializer); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
using System.Globalization; | ||
using System.Linq.Expressions; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public interface IPrintingConfig<TOwner> | ||
{ | ||
IPrintingConfig<TOwner> Exclude<TFieldType>(); | ||
IPrintingConfig<TOwner> Exclude(Expression<Func<TOwner, object>> expression); | ||
IMemberPrintingConfig<TOwner, TFieldType> Serialize<TFieldType>(); | ||
IMemberPrintingConfig<TOwner, TFieldType> Serialize<TFieldType>(Expression<Func<TOwner, TFieldType>> expression); | ||
IPrintingConfig<TOwner> SetCultureFor<TFieldType>(CultureInfo cultureInfo); | ||
IPrintingConfig<TOwner> SetCultureFor(Expression<Func<TOwner, object>> expression, CultureInfo cultureInfo); | ||
IPrintingConfig<TOwner> TrimString(Expression<Func<TOwner, string>> expression, int length); | ||
string PrintToString(TOwner obj); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public class MemberPrintingConfig<TOwner, TFieldType> : IMemberPrintingConfig<TOwner, TFieldType> | ||
{ | ||
private readonly PrintingConfig<TOwner> config; | ||
private readonly MemberInfo? info; | ||
public MemberPrintingConfig(PrintingConfig<TOwner> printingConfig) | ||
{ | ||
config = printingConfig; | ||
} | ||
|
||
public MemberPrintingConfig(PrintingConfig<TOwner> printingConfig, MemberInfo info) | ||
{ | ||
config = printingConfig; | ||
this.info = info; | ||
} | ||
|
||
public IPrintingConfig<TOwner> Config => config; | ||
|
||
public IPrintingConfig<TOwner> With(Func<TFieldType, string> serializer) | ||
{ | ||
if (info != null) | ||
config.AlternativeSerializationForProp.Add(info, o => serializer((TFieldType)o)); | ||
else config.AlternativeSerializationForType.Add(typeof(TFieldType), o => serializer((TFieldType)o)); | ||
return config; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,6 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> | ||
<PackageReference Include="NUnit" Version="4.2.2" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,199 @@ | ||
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; | ||
using ObjectPrinting.Extensions; | ||
|
||
namespace ObjectPrinting | ||
{ | ||
public class PrintingConfig<TOwner> | ||
public class PrintingConfig<TOwner> : IPrintingConfig<TOwner> | ||
{ | ||
private readonly HashSet<Type> simpleTypes = | ||
[ | ||
typeof(bool), typeof(byte), typeof(int), typeof(double), typeof(float), typeof(char), typeof(string), | ||
typeof(DateTime), typeof(TimeSpan), typeof(Guid) | ||
]; | ||
|
||
private readonly HashSet<Type> excludedTypes = new(); | ||
private readonly HashSet<MemberInfo> excludedProps = new(); | ||
private readonly HashSet<object> alreadyProcessed = new(ReferenceEqualityComparer.Instance); | ||
private readonly Dictionary<Type, CultureInfo> cultureForType = new(); | ||
private readonly Dictionary<MemberInfo, CultureInfo> cultureForProp = new(); | ||
private readonly Dictionary<MemberInfo, int> trimLengthForProp = new(); | ||
|
||
protected internal readonly Dictionary<MemberInfo, Func<object, string>> | ||
AlternativeSerializationForProp = new(); | ||
|
||
protected internal readonly Dictionary<Type, Func<object, string>> AlternativeSerializationForType = new(); | ||
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 string PrintToString(TOwner obj) | ||
{ | ||
return PrintToString(obj, 0); | ||
} | ||
|
||
private string PrintToString(object obj, int nestingLevel) | ||
private static MemberInfo GetPropDetails<T>(Expression<Func<TOwner, T>> expression) | ||
{ | ||
//TODO apply configurations | ||
if (obj == null) | ||
return "null" + Environment.NewLine; | ||
return expression.Body switch | ||
{ | ||
MemberExpression memberExpression => memberExpression.Member, | ||
UnaryExpression { Operand: MemberExpression operand } => operand.Member, | ||
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. Не понимаю зачем здесь обрабатывать UnaryExpression. Как будто наоборот тут надо эксепшн кидать 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. А, понял). Это интересно. У тебя в некоторых случаях примитивные типы заворачиваются в object, поэтому тут приходится делать странные вещи. Но кажется такой способ создает странные сайд эффекты. Например можно пытаться применять унарные операторы к ссылочным типам, а это в контексте сериализации вообще смысла никакого не имеет. Короче). Было бы наверное правильнее (с точки зрения бизнес-логики этой библиотеки) Сейчас это можно не делать |
||
_ => throw new ArgumentException("Expression is not a property") | ||
}; | ||
} | ||
|
||
var finalTypes = new[] | ||
private string PrintToString(object? obj, int nestingLevel, MemberInfo? memberInfo = null) | ||
{ | ||
if (obj == null) | ||
return string.Empty; | ||
if (alreadyProcessed.Contains(obj)) | ||
return "[Recursion]"; | ||
return obj switch | ||
{ | ||
typeof(int), typeof(double), typeof(float), typeof(string), | ||
typeof(DateTime), typeof(TimeSpan) | ||
not null when simpleTypes.Contains(obj.GetType()) => ProcessSimpleType(obj, memberInfo), | ||
IDictionary dict => ProcessDictionary(dict, nestingLevel), | ||
IEnumerable list => ProcessCollection(list, nestingLevel), | ||
_ => ProcessNestedObject(obj, nestingLevel) | ||
}; | ||
if (finalTypes.Contains(obj.GetType())) | ||
return obj + Environment.NewLine; | ||
} | ||
|
||
private string ProcessSimpleType(object obj, MemberInfo? memeberInfo = null) | ||
{ | ||
var type = obj.GetType(); | ||
var result = (obj switch | ||
{ | ||
IFormattable f when memeberInfo != null && cultureForProp.TryGetValue(memeberInfo, out var culture) => | ||
f.ToString(null, culture), | ||
IFormattable f when cultureForType.ContainsKey(type) => f.ToString(null, cultureForType[type]), | ||
_ => obj.ToString() | ||
})!; | ||
|
||
if (memeberInfo != null && trimLengthForProp.TryGetValue(memeberInfo, out var length)) | ||
result = result.Length > length ? result[..length] : result; | ||
|
||
var identation = new string('\t', nestingLevel + 1); | ||
return result; | ||
} | ||
|
||
private string ProcessCollection(IEnumerable collection, int nestingLevel) | ||
{ | ||
var sb = new StringBuilder(); | ||
foreach (var item in collection) | ||
sb.Append(PrintToString(item, nestingLevel)); | ||
return sb.ToString(); | ||
} | ||
|
||
private string ProcessDictionary(IDictionary dictionary, int nestingLevel) | ||
{ | ||
var sb = new StringBuilder(); | ||
sb.Append('\t', nestingLevel); | ||
sb.AppendLine(dictionary.GetType().Name); | ||
foreach (var key in dictionary.Keys) | ||
{ | ||
var value = dictionary[key]; | ||
sb.Append('\t', nestingLevel + 1); | ||
sb.AppendLine($"{key.PrintToString()} = {value.PrintToString()}"); | ||
} | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private string ProcessNestedObject(object obj, int nestingLevel) | ||
{ | ||
alreadyProcessed.Add(obj); | ||
var sb = new StringBuilder(); | ||
var type = obj.GetType(); | ||
sb.AppendLine(type.Name); | ||
foreach (var propertyInfo in type.GetProperties()) | ||
|
||
foreach (var property in type.GetProperties()) | ||
{ | ||
if (excludedTypes.Contains(property.PropertyType) || excludedProps.Contains(property)) | ||
continue; | ||
var serializedValue = Serialize(property, obj, nestingLevel + 1); | ||
sb.Append('\t', nestingLevel + 1); | ||
sb.AppendLine($"{property.Name} = {serializedValue}"); | ||
} | ||
|
||
foreach (var field in type.GetFields()) | ||
{ | ||
sb.Append(identation + propertyInfo.Name + " = " + | ||
PrintToString(propertyInfo.GetValue(obj), | ||
nestingLevel + 1)); | ||
if (excludedTypes.Contains(field.FieldType) || excludedProps.Contains(field)) | ||
continue; | ||
var serializedValue = Serialize(field, obj, nestingLevel + 1); | ||
sb.Append('\t', nestingLevel + 1); | ||
sb.AppendLine($"{field.Name} = {serializedValue}"); | ||
} | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private string Serialize(MemberInfo memberInfo, object parent, int nestingLevel) | ||
{ | ||
object value; | ||
Type type; | ||
switch (memberInfo) | ||
{ | ||
case PropertyInfo prop: | ||
value = prop.GetValue(parent); | ||
type = prop.PropertyType; | ||
break; | ||
case FieldInfo field: | ||
value = field.GetValue(parent); | ||
type = field.FieldType; | ||
break; | ||
default: | ||
return string.Empty; | ||
} | ||
if (value == null) | ||
return "null"; | ||
if (AlternativeSerializationForProp.TryGetValue(memberInfo, out var propertySerializer)) | ||
return propertySerializer(value); | ||
if (AlternativeSerializationForType.TryGetValue(type, out var typeSerializer)) | ||
return typeSerializer(value); | ||
return PrintToString(value, nestingLevel, memberInfo); | ||
} | ||
|
||
public IPrintingConfig<TOwner> Exclude<TFieldType>() | ||
{ | ||
excludedTypes.Add(typeof(TFieldType)); | ||
return this; | ||
} | ||
|
||
public IPrintingConfig<TOwner> Exclude(Expression<Func<TOwner, object>> expression) | ||
{ | ||
excludedProps.Add(GetPropDetails(expression)); | ||
return this; | ||
} | ||
|
||
public IMemberPrintingConfig<TOwner, TFieldType> Serialize<TFieldType>() | ||
{ | ||
return new MemberPrintingConfig<TOwner, TFieldType>(this); | ||
} | ||
|
||
public IMemberPrintingConfig<TOwner, TFieldType> Serialize<TFieldType>( | ||
Expression<Func<TOwner, TFieldType>> expression) | ||
{ | ||
return new MemberPrintingConfig<TOwner, TFieldType>(this, GetPropDetails(expression)); | ||
} | ||
|
||
public IPrintingConfig<TOwner> SetCultureFor<TFieldType>(CultureInfo cultureInfo) | ||
{ | ||
cultureForType.Add(typeof(TFieldType), cultureInfo); | ||
return this; | ||
} | ||
|
||
public IPrintingConfig<TOwner> SetCultureFor(Expression<Func<TOwner, object>> expression, | ||
CultureInfo cultureInfo) | ||
{ | ||
cultureForProp.Add(GetPropDetails(expression), cultureInfo); | ||
return this; | ||
} | ||
|
||
public IPrintingConfig<TOwner> TrimString(Expression<Func<TOwner, string>> expression, int length) | ||
{ | ||
trimLengthForProp.Add(GetPropDetails(expression), length); | ||
return this; | ||
} | ||
} | ||
} |
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.
Молодец, что вынес тесты в отдельную сборку (почти полностью). Хорошо бы еще в этом проекте лишние зависимости (от Nunit-а) почистить