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

Большаков Николай #235

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
27 changes: 27 additions & 0 deletions ObjectPrinting/IPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace ObjectPrinting;

internal interface IPrintingConfig<TOwner>
{
Dictionary<Type, IPropertyPrintingConfig<TOwner>> PropertyConfigsByType { get; }

Dictionary<PropertyPath, IPropertyPrintingConfig<TOwner>> PropertyConfigsByPath { get; }

CultureInfo CultureInfo { get; }

bool IsToLimitNestingLevel { get; }

int MaxNestingLevel { get; }

public bool TryGetConfig(
PropertyPath path,
[MaybeNullWhen(false)] out IPropertyPrintingConfig<TOwner> propertyPrintingConfig)
{
var obj = path.PropertyValue.Value;
return PropertyConfigsByPath.TryGetValue(path, out propertyPrintingConfig)
|| obj != null
&& PropertyConfigsByType.TryGetValue(obj.GetType(), out propertyPrintingConfig);
}
}
14 changes: 14 additions & 0 deletions ObjectPrinting/IPropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Globalization;

namespace ObjectPrinting;

internal interface IPropertyPrintingConfig<TOwner>
{
PrintingConfig<TOwner> ParentConfig { get; }

bool IsExcluded { get; }

CultureInfo? CultureInfo { get; }

Func<object, string>? PrintOverride { get; }
}
9 changes: 9 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ObjectPrinting;

public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj)
{
return ObjectPrinter.For<T>().PrintToString(obj);
}
}
199 changes: 194 additions & 5 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,199 @@
namespace ObjectPrinting
using System.Collections;
using System.Reflection;
using System.Text;

namespace ObjectPrinting;

public class ObjectPrinter
{
Comment on lines +6 to 8

Choose a reason for hiding this comment

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

на мой взгляд класс достаточно тяжело читается, классическая рекурсия тут бы выйграла для чтения

public class ObjectPrinter
private static readonly HashSet<Type> _finalTypes =
[
typeof(Guid),
typeof(DateTime),
typeof(TimeSpan),
typeof(string),
];

public static PrintingConfig<T> For<T>()
{
public static PrintingConfig<T> For<T>()
return new PrintingConfig<T>();
}

internal static string PrintToString<TOwner>(TOwner? obj, IPrintingConfig<TOwner> printingConfig)
{
var path = new PropertyPath(new PropertyValue(null, obj));

if (!IsExcluded(path, printingConfig, 0))
{
var sb = new StringBuilder();
AppendPrintedObject(sb, path, printingConfig, 0);
return sb.ToString();
}

return string.Empty;
}

private static void AppendPrintedObject<TOwner>(
StringBuilder sb,
PropertyPath path,
IPrintingConfig<TOwner> printingConfig,
int nestingLevel)
{
var obj = path.PropertyValue.Value;

if (obj == null)
{
sb.AppendLine("null");
}
else if (IsCyclicReference(path))
{
AppendCyclicReference(sb, path);
}
else if (IsAlternativelyPrintedObject(path, printingConfig))
{
AppendAlternativelyPrintedObject(sb, path, printingConfig);
}
else if (IsFinalTypeObject(obj))
{
AppendFinalTypeObject(sb, path, printingConfig);
}
else if (obj is IEnumerable)
{
AppendEnumerableObjectWithItems(sb, path, printingConfig, nestingLevel);
}
else
{
AppendObjectWithProperties(sb, path, printingConfig, nestingLevel);
}
}

private static bool IsCyclicReference(PropertyPath path)
{
var obj = path.PropertyValue.Value!;
return path.Previous != null
&& path.Previous.Contains(obj);
}

private static void AppendCyclicReference(StringBuilder sb, PropertyPath path)
{
var obj = path.PropertyValue.Value!;
var pathToExistingValue = path.Previous!.FindPathTo(obj);
sb.AppendLine($"[root{pathToExistingValue}]");
}

private static bool IsAlternativelyPrintedObject<TOwner>(PropertyPath path, IPrintingConfig<TOwner> printingConfig)
{
return printingConfig.TryGetConfig(path, out var propertyPrintingConfig)
&& propertyPrintingConfig.PrintOverride != null;
}

private static void AppendAlternativelyPrintedObject<TOwner>(
StringBuilder sb,
PropertyPath path,
IPrintingConfig<TOwner> printingConfig)
{
if (printingConfig.TryGetConfig(path, out var propertyPrintingConfig))
{
return new PrintingConfig<T>();
var obj = path.PropertyValue.Value!;
var stringValue = propertyPrintingConfig.PrintOverride!(obj);
sb.AppendLine(stringValue);
}
}
}

private static bool IsFinalTypeObject(object obj)
{
var type = obj.GetType();
return type.IsPrimitive
|| _finalTypes.Contains(type);
}

private static void AppendFinalTypeObject<TOwner>(
StringBuilder sb,
PropertyPath path,
IPrintingConfig<TOwner> printingConfig)
{
var cultureInfo = printingConfig.TryGetConfig(path, out var propertyPrintingConfig)
&& propertyPrintingConfig.CultureInfo != null
? propertyPrintingConfig.CultureInfo
: printingConfig.CultureInfo;

var obj = path.PropertyValue.Value!;
var stringValue = obj is IFormattable formattableObj
? formattableObj.ToString(null, cultureInfo)
: obj.ToString();

sb.AppendLine(stringValue);
}

private static void AppendEnumerableObjectWithItems<TOwner>(
StringBuilder sb,
PropertyPath path,
IPrintingConfig<TOwner> printingConfig,
int nestingLevel)
{
var enumerable = (IEnumerable)path.PropertyValue.Value!;
var type = enumerable.GetType();
var typeName = GetTypeNameWithoutGenericPart(type);
var values = enumerable
.Cast<object>()
.Select((item, i) => new PropertyValue($"{{{i}}}", type, item));

sb.AppendLine(typeName);
AppendPropertyValues(sb, path, printingConfig, values, nestingLevel);
}

private static void AppendObjectWithProperties<TOwner>(
StringBuilder sb,
PropertyPath path,
IPrintingConfig<TOwner> printingConfig,
int nestingLevel)
{
var obj = path.PropertyValue.Value!;
var type = obj.GetType();
var typeName = GetTypeNameWithoutGenericPart(type);
var values = type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(property => property.GetIndexParameters().Length == 0)
.Select(property => new PropertyValue(property, property.GetValue(obj)));

sb.AppendLine(typeName);
AppendPropertyValues(sb, path, printingConfig, values, nestingLevel);
}

private static void AppendPropertyValues<TOwner>(
StringBuilder sb,
PropertyPath path,
IPrintingConfig<TOwner> printingConfig,
IEnumerable<PropertyValue> propertyValues,
int nestingLevel)
{
foreach (var propertyValue in propertyValues)
{
var nextPath = new PropertyPath(propertyValue, path);

if (!IsExcluded(nextPath, printingConfig, nestingLevel + 1))
{
sb.Append('\t', nestingLevel + 1);
sb.Append(propertyValue.Name);
sb.Append(" = ");
AppendPrintedObject(sb, nextPath, printingConfig, nestingLevel + 1);
}
}
}

private static bool IsExcluded<TOwner>(PropertyPath path, IPrintingConfig<TOwner> printingConfig, int nestingLevel)
{
return printingConfig.IsToLimitNestingLevel
&& nestingLevel > printingConfig.MaxNestingLevel
|| printingConfig.TryGetConfig(path, out var propertyPrintingConfig)
&& propertyPrintingConfig!.IsExcluded;
}

private static string GetTypeNameWithoutGenericPart(Type type)
{
var end = type.Name.IndexOf('`');
return end == -1
? type.Name
: type.Name[..end];
}
}
7 changes: 2 additions & 5 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<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>
Loading