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

Бочаров Александр #222

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
611a707
add(ObjectPrintingTests.csproj): инитнул тесты
Geratoptus Dec 7, 2024
9759827
delete(Solved and Tests): удалил Solved и тесты из ObjectPrinting
Geratoptus Dec 7, 2024
4a8b70d
init(ObjectPrinting): инитнул архитектуру ObjectPrinting
Geratoptus Dec 7, 2024
5bb738f
init(ObjectPrintingTests.csproj): инитнул тесты
Geratoptus Dec 7, 2024
1e0767a
fix(ObjectPrintingTests): поправил версии в сборке
Geratoptus Dec 7, 2024
a5c8873
add(ObjectPrintingTests): закрепил функциональность в тестах на исклю…
Geratoptus Dec 7, 2024
9f09fc6
feat(ObjectPrinting): реализовал исключение свойств определенного тип…
Geratoptus Dec 7, 2024
9876333
feat(ObjectPrintingTests): добавил в тесты обрезание строк, культуру …
Geratoptus Dec 8, 2024
cd8dcfc
feat(ObjectPrinting): реализовал обрезание строк, культуру и проверку…
Geratoptus Dec 8, 2024
fe94dd9
feat(ObjectPrinterAcceptanceTests): добавил в acceptance test PrintTo…
Geratoptus Dec 8, 2024
f41c3af
refactor(ObjectPrinterTest): добавил SetUp, поправил нейминг, + тест …
Geratoptus Dec 8, 2024
c9c9920
refactor(ObjectPrinting): вынес логику во вспомогательные методы, поп…
Geratoptus Dec 8, 2024
729f353
feat(ObjectPrintingTests): добавил тесты на сериализацию коллекций
Geratoptus Dec 8, 2024
0d5b655
feat(ObjectPrinting): реализовал сериализацию коллекций + рефакторинг
Geratoptus Dec 8, 2024
c8b394e
add(Serializer.cs): вынес логику сериализации в отдельный класс
Geratoptus Dec 11, 2024
6d1f9a2
feat(MemberPrintingConfigExtensions): убрал possible null reference
Geratoptus Dec 11, 2024
eb59b10
feat(ObjectPrintingTests/Person): расширил класс, добавил конструктор
Geratoptus Dec 11, 2024
82521df
feat(ObjectPrinterTest.cs): внедрил новый Person в тесты
Geratoptus Dec 11, 2024
cf8a079
feat(ObjectPrinterAcceptanceTests): улучшил нейминг, добавил примеры …
Geratoptus Dec 11, 2024
a0238da
refactor(ObjectPrinting): убрал ненужные using'и, поправил warning'и
Geratoptus Dec 11, 2024
f7c5cb4
feat(Serializer.cs): унифицировал сообщение в соответствии с остальными
Geratoptus Dec 11, 2024
cb47a5d
feat(ObjectPrintingTests): захотел написать тесты по-нормальному, чер…
Geratoptus Dec 12, 2024
ef8a664
refactor(Person): поправил переносы
Geratoptus Dec 12, 2024
7ca11ca
add(ObjectPrinterApprovalTests): заменил позорные тесты, на ApprovalT…
Geratoptus Dec 12, 2024
5d1b557
fix(PrintingConfig): теперь нельзя передать любой делегат, а только o…
Geratoptus Dec 13, 2024
7377eda
feat(MemberPrintingConfig): внедрил изменение из PrintingConfig
Geratoptus Dec 13, 2024
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
30 changes: 30 additions & 0 deletions ObjectPrinting/MemberPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Reflection;

namespace ObjectPrinting;

public class MemberPrintingConfig<TOwner, TMemberType>(
PrintingConfig<TOwner> printingConfig, MemberInfo? memberInfo = null)
: IMemberPrintingConfig<TOwner>
{
internal readonly PrintingConfig<TOwner> PrintingConfig = printingConfig;
internal readonly MemberInfo? MemberInfo = memberInfo;

public PrintingConfig<TOwner> Using(Func<TMemberType, string> printingMethod)
{
var resultDelegate = new Func<object, string>(obj => printingMethod((TMemberType)obj));
if (MemberInfo is null)
PrintingConfig.CustomTypeSerializers[typeof(TMemberType)] = resultDelegate;
else
PrintingConfig.CustomMemberSerializers[MemberInfo] = resultDelegate;

return PrintingConfig;
}

PrintingConfig<TOwner> IMemberPrintingConfig<TOwner>.ParentConfig => PrintingConfig;
}

public interface IMemberPrintingConfig<TOwner>

Choose a reason for hiding this comment

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

Не очень хорошая история объявлять интерфейсы в файле класса - наследника, даже если он один. Лучше всё-таки вынести в отдельный файл

{
PrintingConfig<TOwner> ParentConfig { get; }
}
37 changes: 37 additions & 0 deletions ObjectPrinting/MemberPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Globalization;

namespace ObjectPrinting;

public static class MemberPrintingConfigExtensions
{
public static string PrintToString<T>(this T? obj, Func<PrintingConfig<T>, PrintingConfig<T>> config)
{
return config(ObjectPrinter.For<T>()).PrintToString(obj);
}

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this MemberPrintingConfig<TOwner, string> memberConfig, int maxLen)
{
if (maxLen < 0)
throw new ArgumentException("Length to trim must be non negative");

var memberInfo = memberConfig.MemberInfo;

if (memberInfo == null)
memberConfig.PrintingConfig.TrimStringLength = maxLen;
else
memberConfig.PrintingConfig.TrimmedMembers[memberInfo] = maxLen;

return memberConfig.PrintingConfig;
}

public static PrintingConfig<TOwner> WithCulture<TOwner, TMemberType>(
this MemberPrintingConfig<TOwner, TMemberType> memberConfig, CultureInfo culture)
where TMemberType : IFormattable
{
memberConfig.PrintingConfig.CulturesForTypes[typeof(TMemberType)] = culture;

return memberConfig.PrintingConfig;
}
}
7 changes: 7 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ObjectPrinting;

public static class ObjectExtensions
{
public static string PrintToString<T>(this T? obj)
=> ObjectPrinter.For<T>().PrintToString(obj);
}
12 changes: 4 additions & 8 deletions ObjectPrinting/ObjectPrinter.cs
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();
}
92 changes: 61 additions & 31 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,71 @@
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;

namespace ObjectPrinting
namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
{
public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}
internal readonly HashSet<Type> ExcludedTypes = [];
internal readonly HashSet<MemberInfo?> ExcludedMembers = [];

private string PrintToString(object obj, int nestingLevel)
internal Dictionary<MemberInfo, int> TrimmedMembers { get; } = new();
internal Dictionary<Type, CultureInfo> CulturesForTypes { get; } = new();
internal Dictionary<Type, Func<object, string>> CustomTypeSerializers { get; } = new();
internal Dictionary<MemberInfo, Func<object, string>> CustomMemberSerializers { get; } = new();

internal int? TrimStringLength;
private int maxNestingLevel = 5;

public int MaxNestingLevel
{
get => maxNestingLevel;
private set
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var finalTypes = new[]
if (value <= 0)
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;

var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
throw new ArgumentOutOfRangeException(
nameof(value), value, "The maxNestingDepth value must be positive.");
}
return sb.ToString();
maxNestingLevel = value;
}
}

public MemberPrintingConfig<TOwner, TMemberType> SetPrintingFor<TMemberType>() => new(this);

public MemberPrintingConfig<TOwner, TMemberType> SetPrintingFor<TMemberType>(
Expression<Func<TOwner, TMemberType>> memberSelector)
{
var expression = (MemberExpression)memberSelector.Body;
return new MemberPrintingConfig<TOwner, TMemberType>(this, expression.Member);
}

public PrintingConfig<TOwner> Exclude<TMemberType>(Expression<Func<TOwner, TMemberType>> memberSelector)
{
var expression = (MemberExpression)memberSelector.Body;
ExcludedMembers.Add(expression.Member);

return this;
}

public PrintingConfig<TOwner> Exclude<TMemberType>()
{
ExcludedTypes.Add(typeof(TMemberType));
return this;
}

public PrintingConfig<TOwner> SetSerializationDepth(int depth)
{
MaxNestingLevel = depth;
return this;
}

public string PrintToString(TOwner? obj)
{
var serializer = new Serializer<TOwner>(this);

return serializer.SerializeObject(obj);
}
}
152 changes: 152 additions & 0 deletions ObjectPrinting/Serializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ObjectPrinting;

public class Serializer<TOwner>(PrintingConfig<TOwner> config)
{
public string SerializeObject(TOwner? obj)
{
return PrintToString(obj, 0);
}

private string PrintToString(object? obj, int nestingLevel)
{
if (nestingLevel > config.MaxNestingLevel)
{
return $"The MaxNestingLevel of serialization was reached - {config.MaxNestingLevel}" + Environment.NewLine;
}

if (obj == null)
{
return "null" + Environment.NewLine;
}

var indentation = GetIndentation(nestingLevel);

if (config.CulturesForTypes.TryGetValue(obj.GetType(), out var culture))
return ((IFormattable)obj).ToString(null, culture) + Environment.NewLine;

if (IsTypeFinal(obj.GetType()))
{
var trimLen = config.TrimStringLength;
var printObj = obj.ToString();
if (obj is string && trimLen != null)
printObj = printObj?.Length >= trimLen ? printObj[..(int)trimLen] : printObj;
return printObj + Environment.NewLine;
}

var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);

if (obj is IEnumerable enumerable)
sb.Append(GetPrintedCollection(enumerable, nestingLevel));
else
foreach (var memberInfo in type.GetMembers().Where(IsPropertyOrField))
{
if (config.ExcludedTypes.Contains(GetMemberType(memberInfo)) ||
config.ExcludedMembers.Contains(memberInfo))
continue;

var printingResult = GetPrintingResult(obj, memberInfo, nestingLevel);

sb.Append(indentation + memberInfo.Name + " = " + printingResult);

Choose a reason for hiding this comment

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

Вместо конкатенации лучше использовать интерполяцию

}

return sb.ToString();
}

private string GetPrintedCollection(IEnumerable obj, int nestingLevel)
{
if (obj is IDictionary dict)
return GetPrintedDictionary(dict, nestingLevel);

return GetPrintedSequence(obj, nestingLevel);
}

private string GetPrintedSequence(IEnumerable obj, int nestingLevel)

Choose a reason for hiding this comment

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

IEnumerable заслуживает чего-то большего чем просто obj) Ну а если серьёзно, то именование всё-таки важно, нижний цикл выглядит очень странно, будто бы мы пытаемся итерироваться по объекту...

{
var sb = new StringBuilder();
var indentation = GetIndentation(nestingLevel);
var index = 0;
foreach (var element in obj)
sb.Append($"{indentation}{index++}: {PrintToString(element, nestingLevel + 1)}");

return sb.ToString();
}

private string GetPrintedDictionary(IDictionary dict, int nestingLevel)
{
var sb = new StringBuilder();
var indentation = GetIndentation(nestingLevel);
foreach (DictionaryEntry pair in dict)
sb.Append($"{indentation}{pair.Key}: {PrintToString(pair.Value, nestingLevel + 1)}");

return sb.ToString();
}

private string GetPrintingResult(object obj, MemberInfo memberInfo, int nestingLevel)
{
string printingResult;

if (config.CustomTypeSerializers.TryGetValue(GetMemberType(memberInfo), out var typeSerializer))
{
printingResult = typeSerializer.DynamicInvoke(GetMemberValue(memberInfo, obj)) + Environment.NewLine;
}
else if (config.CustomMemberSerializers.TryGetValue(memberInfo, out var propertySerializer))
{
printingResult = propertySerializer.DynamicInvoke(GetMemberValue(memberInfo, obj)) + Environment.NewLine;
}
else
{
printingResult = PrintToString(GetMemberValue(memberInfo, obj),
nestingLevel + 1);
}

return TrimResultIfNeeded(memberInfo, printingResult);
}

private string TrimResultIfNeeded(MemberInfo memberInfo, string printingResult)
{
if (config.TrimmedMembers.TryGetValue(memberInfo, out var length))
printingResult = printingResult.Length >= length
? printingResult[..length] + Environment.NewLine
: printingResult;

return printingResult;
}

private static object? GetMemberValue(MemberInfo member, Object obj)
{
if (!IsPropertyOrField(member))
throw new ArgumentException("Provided member must be Field or Property");

return member.MemberType == MemberTypes.Field

Choose a reason for hiding this comment

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

Есть некоторая проблема... Верхний if проверяет на то что member может быть только двух типов, но в реальности у нас может быть ситуация, что кто-то по невнимательности изменит метод проверки IsPropertyOrField, забыв поменять здесь логику, в этом случае тернарник станет выкидывать, (сейчас могу соврать) CastException-ы, что не очень наглядно.

По коду вижу, что с is ты знаком, поэтому я бы рекомендовала использовать switch с pattern matching-ом, который опишет и логику извлечения данных и строго ограничит типы из которых эти данные можно извлечь. В этом случае весь этот метод выглядел бы как-то так:

private static object? GetMemberValue(MemberInfo member, Object obj)  
{  
        return member switch
        {
            _ when member is FieldInfo field => field.GetValue(obj),
            _ when member is PropertyInfo property => property.GetValue(obj),
            _ => throw new ArgumentException("Provided member must be Field or Property")
        };
}

Ну или так:

private static object? GetMemberValue(MemberInfo member, Object obj)
{
        return member switch
        {
            FieldInfo field => field.GetValue(obj),
            PropertyInfo property => property.GetValue(obj),
            _ => throw new ArgumentException("Provided member must be Field or Property")
        };
}

? ((FieldInfo)member).GetValue(obj)
: ((PropertyInfo)member).GetValue(obj);
}

private static Type GetMemberType(MemberInfo member)

Choose a reason for hiding this comment

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

Ну, думаю, здесь понятно что нужно сделать)

{
if (!IsPropertyOrField(member))
throw new ArgumentException("Provided member must be Field or Property");

return member.MemberType == MemberTypes.Field
? ((FieldInfo)member).FieldType
: ((PropertyInfo)member).PropertyType;
}

private static bool IsPropertyOrField(MemberInfo member)
=> member.MemberType is MemberTypes.Field or MemberTypes.Property;


private static bool IsTypeFinal(Type type)
=> type.IsPrimitive || type == typeof(string) || type == typeof(Guid);


private static string GetIndentation(int nestingLevel) => new('\t', nestingLevel + 1);
}
10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectExtensions.cs

This file was deleted.

10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectPrinter.cs

This file was deleted.

Loading