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

Шестопалов Андрей #219

Open
wants to merge 2 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
26 changes: 26 additions & 0 deletions ObjectPrinting/Extensions/DictionarySerializationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace ObjectPrinting.Extensions;

public static class DictionarySerializationExtensions
{
public static string SerializeDictionary(this IDictionary dictionary, int nestingLevel, HashSet<object> visitedObjects, Func<object, int, HashSet<object>, string> serializer)
{
var dictionaryType = dictionary.GetType().Name;
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
sb.AppendLine($"{dictionaryType}:");

foreach (DictionaryEntry entry in dictionary)
{
sb.Append($"{indentation}Key = {serializer(entry.Key, nestingLevel + 1, visitedObjects)}");
sb.Append($"{indentation}Value = {serializer(entry.Value, nestingLevel + 1, visitedObjects)}");
}

visitedObjects.Remove(dictionary);
return sb.ToString();
}
}
25 changes: 25 additions & 0 deletions ObjectPrinting/Extensions/EnumerableSerializationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace ObjectPrinting.Extensions;

public static class EnumerableSerializationExtensions
{
public static string SerializeEnumerable(this IEnumerable enumerable, int nestingLevel, HashSet<object> visitedObjects, Func<object, int, HashSet<object>, string> serializer)
{
var collectionType = enumerable.GetType().Name;
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
sb.AppendLine($"{collectionType}:");

foreach (var element in enumerable)
{
sb.Append($"{indentation}- {serializer(element, nestingLevel + 1, visitedObjects)}");
}

visitedObjects.Remove(enumerable);
return sb.ToString();
}
}
39 changes: 39 additions & 0 deletions ObjectPrinting/Extensions/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting;

public static class PropertyPrintingConfigExtensions
{
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 PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
if (maxLen < 0)
{
throw new ArgumentOutOfRangeException(nameof(maxLen), "Max length must be non-negative");
}

IPropertyPrintingConfig<TOwner, string> propertyConfig = propConfig;
var printingConfig = propertyConfig.ParentConfig;

var memberSelector = propConfig.MemberSelector;

var propertyName = ((MemberExpression)memberSelector!.Body).Member.Name;

printingConfig.AddPropertySerializer(propertyName, value =>
{
var stringValue = value as string ?? string.Empty;

return stringValue.Length > maxLen
? stringValue[..maxLen]
: stringValue;
});

return printingConfig;
}
}
6 changes: 6 additions & 0 deletions ObjectPrinting/IPropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ObjectPrinting;

public interface IPropertyPrintingConfig<TOwner, TPropType>
{
PrintingConfig<TOwner> ParentConfig { get; }
}
9 changes: 9 additions & 0 deletions ObjectPrinting/ObjectExtentions.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);
}
}
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 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="ApprovalTests" Version="7.0.0-beta.3" />
<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
169 changes: 144 additions & 25 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,160 @@
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Text;
using ObjectPrinting.Extensions;

namespace ObjectPrinting
namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
private readonly Dictionary<Type, Func<object, string>> typeSerializers = new();

Choose a reason for hiding this comment

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

Давай по смыслу сгруппируем поля. typeSerializers с propertySerializers например, и excludingTypes с excludingProperties

private readonly Dictionary<Type, CultureInfo> typeCultures = new();

private readonly Dictionary<string, Func<object, string>> propertySerializers = new();

private readonly List<Type> excludingTypes = [];
private readonly HashSet<string> excludingProperties = [];

private static readonly HashSet<Type> FinalTypes = new()
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};

public PrintingConfig<TOwner> Excluding<TPropType>()
{
excludingTypes.Add(typeof(TPropType));
return this;
}

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
if (memberSelector.Body is MemberExpression memberExpression)
{
excludingProperties.Add(memberExpression.Member.Name);
}
else
{
throw new ArgumentException("Expression must be a member access expression", nameof(memberSelector));
}

return this;
}

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>()
{
return new PropertyPrintingConfig<TOwner, TPropType>(this);
}

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector)
{
return new PropertyPrintingConfig<TOwner, TPropType>(this, memberSelector);
}

public void AddTypeSerializer<TPropType>(Func<TPropType, string> serializer)
{
public string PrintToString(TOwner obj)
typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj);
}

public void AddPropertySerializer(string propertyName, Func<object, string> serializer)

Choose a reason for hiding this comment

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

Получается для каждого типа будем свой отдельный метод добавлять? Можно завести отдельно TypeSerializer для каждого типа, и отдельные реализации для каждого поддерживаемого типа. Тогда получим несколько небольших классов, и у каждого будет своя понятная и небольшая зона ответственности. Кажется тогда этот класс будет не таким раздутым

Copy link
Author

Choose a reason for hiding this comment

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

Мне кажется это не очень хорошая идея, ведь реализация сериализации задаётся функцией Func<TPropType, string>, а значит нам нужно хранить эту функцию и использовать в нужном месте. Не очень понимаю, как хранение этой функции в объекте поможет упростить наш класс. Мы ведь только добавим накладные ресурсы на создание каждого объекта, вместо того, чтобы сразу хранить функцию в словаре.

Кроме того, я постарался максимально упростить класс ObjectPrinting :)

{
propertySerializers[propertyName] = serializer;
}

public void AddTypeCulture<TPropType>(CultureInfo culture)
{
typeCultures[typeof(TPropType)] = culture;
}

public string PrintToString(TOwner obj)
{
return PrintToStringInternal(obj, 0, []);
}

private string PrintToStringInternal(object? obj, int nestingLevel, HashSet<object> visitedObjects)
{
if (obj is null)
{
return AddNewLine("null");
}

if (!visitedObjects.Add(obj))
{
return PrintToString(obj, 0);
return AddNewLine("(cyclic reference detected)");
}

private string PrintToString(object obj, int nestingLevel)
var type = obj.GetType();

if (typeSerializers.TryGetValue(type, out var serializer))
{
return AddNewLine(serializer(obj));
}

if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;
return AddNewLine(formattable.ToString(null, culture));
}

if (FinalTypes.Contains(type) || type.IsEnum)
{
return AddNewLine(obj.ToString());
}

if (obj is IDictionary dictionary)
{
return dictionary.SerializeDictionary(nestingLevel, visitedObjects, PrintToStringInternal);
}

if (obj is IEnumerable enumerable)
{
return enumerable.SerializeEnumerable(nestingLevel, visitedObjects, PrintToStringInternal);
}


var finalTypes = new[]
return HandleComplexObject(obj, type, nestingLevel, visitedObjects);
}

private static string AddNewLine(string? input)
{
return input + Environment.NewLine;
}

private static string GetIndentation(int nestingLevel)
{
return new string('\t', nestingLevel + 1);
}

private string HandleComplexObject(object obj, Type type, int nestingLevel, HashSet<object> visitedObjects)
{
var indentation = GetIndentation(nestingLevel);
var result = new StringBuilder();
result.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
{
if (excludingTypes.Contains(propertyInfo.PropertyType) ||
excludingProperties.Contains(propertyInfo.Name))
{
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())
continue;
}

var propertyName = propertyInfo.Name;
var propertyValue = propertyInfo.GetValue(obj);

if (propertySerializers.TryGetValue(propertyName, out var propertySerializer))
{

Choose a reason for hiding this comment

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

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

sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
result.AppendLine($"{indentation}{propertyName} = {propertySerializer(propertyValue!)}");
}
else
{
result.Append($"{indentation}{propertyName} = {PrintToStringInternal(propertyValue, nestingLevel + 1, visitedObjects)}");
}
return sb.ToString();
}

visitedObjects.Remove(obj);
return result.ToString();
}
}
38 changes: 38 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Globalization;
using System.Linq.Expressions;

namespace ObjectPrinting;

public class PropertyPrintingConfig<TOwner, TPropType> : PrintingConfig<TOwner>, IPropertyPrintingConfig<TOwner, TPropType>
{
private readonly PrintingConfig<TOwner> printingConfig;
public readonly Expression<Func<TOwner, TPropType>>? MemberSelector;

public PropertyPrintingConfig(PrintingConfig<TOwner> printingConfig,
Expression<Func<TOwner, TPropType>>? memberSelector = null)
{
this.printingConfig = printingConfig;
MemberSelector = memberSelector;
}

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
printingConfig.AddTypeSerializer(print);
return printingConfig;
}

public PrintingConfig<TOwner> Using(CultureInfo culture)
{
if (!typeof(TPropType).IsValueType || !typeof(IFormattable).IsAssignableFrom(typeof(TPropType)))
{
throw new InvalidOperationException(
$"Culture can only be applied to numeric or formattable types. Type: {typeof(TPropType)}");
}

printingConfig.AddTypeCulture<TPropType>(culture);
return printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
}
9 changes: 9 additions & 0 deletions ObjectPrinting/Tests/Building.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace ObjectPrinting.Tests;

public class Building
{
public string Name { get; set; }
public Dictionary<int, List<Room>> Floors { get; set; }
}
8 changes: 8 additions & 0 deletions ObjectPrinting/Tests/Company.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ObjectPrinting.Tests
{
public class Company
{
public string Name { get; set; }
public Department MainDepartment { get; set; }
}
}
9 changes: 9 additions & 0 deletions ObjectPrinting/Tests/Department.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ObjectPrinting.Tests
{
public class Department
{
public string Name { get; set; }
public Company ParentCompany { get; set; }
public Department SubDepartment { get; set; }
}
}
Loading