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

Зарипов Кирилл #210

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
11 changes: 5 additions & 6 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace ObjectPrinting
namespace ObjectPrinting;

public static class ObjectPrinter
{
public class ObjectPrinter
public static PrintingConfig<T> For<T>()
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
return new PrintingConfig<T>();
}
}
1 change: 1 addition & 0 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
Expand Down
182 changes: 158 additions & 24 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,175 @@
using System;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Linq.Expressions;
using System.Collections.Generic;

namespace ObjectPrinting
namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
private readonly HashSet<Type> finalTypes =
[
typeof(int), typeof(double), typeof(float), typeof(string), typeof(DateTime), typeof(TimeSpan), typeof(Guid)
];

private readonly HashSet<Type> excludedTypes = [];
private readonly HashSet<string> excludedProperties = [];
private readonly HashSet<object> processedObjects = [];

Choose a reason for hiding this comment

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

Не уверен, но скорее всего в конструктор HashSet'а нужно передать ReferenceEqualityComparer.Instance. Иначе будет использоваться Equals объекта и есть вероятность, что "совпадут" два одинаковых по состоянию, но разных по ссылке объекта

Copy link
Author

Choose a reason for hiding this comment

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

private readonly Dictionary<Type, Func<object, string>> typeSerializers = new();
private readonly Dictionary<string, Func<object, string>> propertySerializers = new();
private IFormatProvider numberCulture = CultureInfo.InvariantCulture;
private int? maxStringLength;

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

public PrintingConfig<TOwner> Exclude<T>(Expression<Func<TOwner, T>> propertyExpression)
{
var propertyName = GetPropertyName(propertyExpression);
excludedProperties.Add(propertyName);
return this;
}

public PrintingConfig<TOwner> SetCustomSerialization<T>(Func<T, string> serializer)
{
typeSerializers[typeof(T)] = obj => serializer((T)obj);
return this;
}

public PrintingConfig<TOwner> SetCustomSerialization<T>(Expression<Func<TOwner, T>> propertyExpression,
Func<T, string> serializer)
{
var propertyName = GetPropertyName(propertyExpression);
propertySerializers[propertyName] = obj => serializer((T)obj);
return this;
}

public PrintingConfig<TOwner> SetCulture(IFormatProvider culture)

Choose a reason for hiding this comment

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

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

Copy link
Author

Choose a reason for hiding this comment

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

{
numberCulture = culture;
return this;
}

public PrintingConfig<TOwner> TrimStringsToLength(int maxLength)
{
maxStringLength = maxLength;
return this;
}

private static string GetPropertyName<T>(Expression<Func<TOwner, T>> propertyExpression)
{
if (propertyExpression.Body is not MemberExpression member)
{
throw new ArgumentException("Expression must be a property access.", nameof(propertyExpression));
}

var propertyName = member.Member.Name;
return propertyName;
}

public string PrintToString(TOwner obj) => PrintToString(obj, 0);

private string PrintToString(object? obj, int nestingLevel)
{
public string PrintToString(TOwner obj)
if (obj == null)
{
return PrintToString(obj, 0);
return "null" + Environment.NewLine;
}

private string PrintToString(object obj, int nestingLevel)
if (processedObjects.Contains(obj))
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;
return "[Circular Reference]" + Environment.NewLine;
}

if (TrySerializeFinalType(obj, out var result))
{
return result;
}

var finalTypes = new[]
if (TrySerializeCollection(obj, nestingLevel, out var collectionResult))
{
return collectionResult;
}

return SerializeComplexType(obj, nestingLevel);
}

private bool TrySerializeCollection(object obj, int nestingLevel, out string collectionResult)
{
if (obj is IEnumerable enumerable)
{
var text = new StringBuilder();
var pad = new string('\t', nestingLevel + 1);

text.AppendLine($"{obj.GetType().Name}:");
foreach (var item in enumerable)
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
text.Append(pad + PrintToString(item, nestingLevel + 1));
}

collectionResult = text.ToString();
return true;
}

collectionResult = null;
return false;
}

private bool TrySerializeFinalType(object obj, out string result)
{
if (finalTypes.Contains(obj.GetType()))
{
result = obj switch
{
IFormattable formattable => formattable.ToString(null, numberCulture) + Environment.NewLine,
string str when maxStringLength.HasValue => string.Concat(

Choose a reason for hiding this comment

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

Круто что знаешь про Span, но при наличии ограничения на длину, сама строка может быть сразу подходящей и тогда всё это преобразование будет избыточным. Кстати, что будет, если сравнить str.Length < (или >) int?(null)?

Copy link
Author

Choose a reason for hiding this comment

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

Будет False в обоих случаях

str.AsSpan(0, Math.Min(str.Length, maxStringLength.Value)), Environment.NewLine),
_ => obj + Environment.NewLine
};
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())
return true;
}

result = null;
return false;
}

private string SerializeComplexType(object obj, int nestingLevel)
{
processedObjects.Add(obj);
var pad = new string('\t', nestingLevel + 1);
var text = new StringBuilder();
var type = obj.GetType();
text.AppendLine(type.Name);

foreach (var property in type.GetProperties())
{
if (excludedTypes.Contains(property.PropertyType) || excludedProperties.Contains(property.Name))
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
continue;
}
return sb.ToString();

var value = property.GetValue(obj);
var serializedValue = SerializeProperty(property, value, nestingLevel + 1);
text.Append(pad + $"{property.Name} = {serializedValue}");
}

return text.ToString();
}

private string SerializeProperty(PropertyInfo property, object value, int nestingLevel)
{
if (propertySerializers.TryGetValue(property.Name, out var propertySerializer))
return propertySerializer(value) + Environment.NewLine;

if (typeSerializers.TryGetValue(property.PropertyType, out var typeSerializer))
return typeSerializer(value) + Environment.NewLine;

return PrintToString(value, nestingLevel);
}
}
15 changes: 15 additions & 0 deletions ObjectPrinting/PrintingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace ObjectPrinting;

public static class PrintingExtensions

Choose a reason for hiding this comment

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

nit: Операция выглядит слишком специфичной, чтобы быть расширением. Я бы завел статические методы в ObjectPrinter

Copy link
Author

Choose a reason for hiding this comment

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

{
public static string Serialize<T>(this T obj) =>
new PrintingConfig<T>().PrintToString(obj);

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