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

Мажирин Александр #231

Open
wants to merge 6 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
17 changes: 17 additions & 0 deletions ObjectPrinting/Extensions/MemberPrintingConfingExtentions.cs
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
{
Expand Down
11 changes: 11 additions & 0 deletions ObjectPrinting/IMemberPrintingConfig.cs
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);
}
18 changes: 18 additions & 0 deletions ObjectPrinting/IPrintingConifg.cs
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);
}

31 changes: 31 additions & 0 deletions ObjectPrinting/MemberPrintingConfig.cs
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;
}
}
8 changes: 1 addition & 7 deletions ObjectPrinting/ObjectPrinting.csproj
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>

Choose a reason for hiding this comment

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

Молодец, что вынес тесты в отдельную сборку (почти полностью). Хорошо бы еще в этом проекте лишние зависимости (от Nunit-а) почистить

<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>
188 changes: 173 additions & 15 deletions ObjectPrinting/PrintingConfig.cs
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();

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

Там кстати есть экстеншн TrimmedToLength, который ты не используешь. Он как бы намекает, что это какой-то частный случай, который для удобства выписали отдельно.


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,

Choose a reason for hiding this comment

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

Не понимаю зачем здесь обрабатывать UnaryExpression. Как будто наоборот тут надо эксепшн кидать

Choose a reason for hiding this comment

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

А, понял). Это интересно. У тебя в некоторых случаях примитивные типы заворачиваются в object, поэтому тут приходится делать странные вещи. Но кажется такой способ создает странные сайд эффекты. Например можно пытаться применять унарные операторы к ссылочным типам, а это в контексте сериализации вообще смысла никакого не имеет.

Короче). Было бы наверное правильнее (с точки зрения бизнес-логики этой библиотеки)
добавить дженерик параметр в методы типа такого
public PrintingConfig<TOwner> Exclude<TProp>(Expression<Func<TOwner, TProp>> expression)
чтобы не происходило каста к 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;
}
}
}
10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectPrinter.cs

This file was deleted.

62 changes: 0 additions & 62 deletions ObjectPrinting/Solved/PrintingConfig.cs

This file was deleted.

Loading