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

Рыбин Леонид #229

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
16 changes: 16 additions & 0 deletions ObjectPrinting/Extensions/NumericCultureExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Globalization;

namespace ObjectPrinting.Extensions;

public static class NumericCultureExtension
{
public static PrintingConfig<TOwner> UseCulture<TOwner, TNumericType>(
this PrintingConfiguration<TOwner, TNumericType> propertyStringConfiguration,
CultureInfo culture) where TNumericType : IFormattable
{
propertyStringConfiguration.ParentConfig.AddNumericCulture<TNumericType>(culture);

Choose a reason for hiding this comment

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

А почему только Numeric? У DateTime'а тоже есть Культура.
В условиях написано "Для всех типов, имеющих культуру, есть возможность ее указать"


return propertyStringConfiguration.ParentConfig;
}
}
12 changes: 12 additions & 0 deletions ObjectPrinting/Extensions/ObjectPrinterExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace ObjectPrinting.Extensions;

public static class ObjectPrinterExtension
{
public static string PrintToString<T>(this T obj) =>
ObjectPrinter.For<T>().PrintToString(obj);

public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config) =>
config(ObjectPrinter.For<T>()).PrintToString(obj);
}
13 changes: 13 additions & 0 deletions ObjectPrinting/Extensions/StringPrintingConfigurationExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ObjectPrinting.Extensions;

public static class StringPrintingConfigurationExtension
{
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PrintingConfiguration<TOwner, string> propertyStringConfiguration, int length)
{
propertyStringConfiguration.ParentConfig
.AddStringPropertyTrim(propertyStringConfiguration.PropertyMemberInfo.Name, length);

return propertyStringConfiguration.ParentConfig;
}
}
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
171 changes: 159 additions & 12 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,188 @@
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;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
private readonly HashSet<Type> excludedTypes = new();
private readonly HashSet<MemberInfo> excludedProperties = new();
private readonly Dictionary<Type, Delegate> typeSerializers = new(); // ���������� �������

Choose a reason for hiding this comment

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

Сделай UTF8, пожалуйста, а не cp1251. Комментарии ломаются

Choose a reason for hiding this comment

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

А почему мы здесь делегаты сохраняем? Почему просто Фанку не сохранять, которая нам явно строку вернет?

private readonly Dictionary<string, Delegate> propertySerializers = new(); // ���������� �������
private readonly Dictionary<Type, CultureInfo> typeCultures = new();
private readonly Dictionary<string, int> propertyTrim = new();
private int MaxNestingLevel = 5;

private readonly HashSet<Type> finalTypes =
[
typeof(bool), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float),
typeof(double), typeof(decimal), typeof(string), typeof(DateTime), typeof(TimeSpan), typeof(Guid)
];

// 1. ��������� �� ������������ �������� ������������� ����
public PrintingConfig<TOwner> Exclude<TPropType>()
{
excludedTypes.Add(typeof(TPropType));
return this;
}

// 2. ������� �������������� ������ ������������ ��� ������������� ����
public PrintingConfiguration<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> propertySelector)
{
var memberInfo = GetPropertyName(propertySelector);
return new PrintingConfiguration<TOwner, TPropType>(this, memberInfo);
}

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

// 6. ��������� �� ������������ ����������� ��������
public PrintingConfig<TOwner> Exclude<TPropType>(Expression<Func<TOwner, TPropType>> propertySelector)
{
var propertyName = GetPropertyName(propertySelector);
excludedProperties.Add(propertyName);
return this;
}

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


private string PrintToString(object obj, int nestingLevel)
{
//TODO apply configurations
if (nestingLevel > MaxNestingLevel)
return "��������� �������� ������� ������������";

Choose a reason for hiding this comment

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

Если мы так начинаем обрабатывать циклические зависимости, то правда не упадет StackOverflowException, но для пользователей тоже не совсем очевидное поведение будет:
image

Copy link
Author

Choose a reason for hiding this comment

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

Добавил сеттер на максимальную длинну рекурсии


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

var type = obj.GetType();

if (excludedTypes.Contains(type))
return string.Empty;

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

if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable)
return formattable.ToString(null, culture);

if (typeSerializers.TryGetValue(type, out var serializer))
return serializer.DynamicInvoke(obj).ToString();

if (obj is ICollection collection)
return SerializeCollection(collection, nestingLevel);

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));
var propertyValue = propertyInfo.GetValue(obj);
var propertyType = propertyInfo.PropertyType;
var propertyName = propertyInfo.Name;

if (excludedProperties.Contains(propertyInfo))
continue;
if (excludedTypes.Contains(propertyType))
continue;

if (typeSerializers.TryGetValue(propertyType, out var typeSerializer))
{
sb.AppendLine($"{identation}{propertyName} = {typeSerializer.DynamicInvoke(propertyValue)}");
continue;
}

if (propertySerializers.TryGetValue(propertyName, out var propertySerializer))
{
sb.AppendLine($"{identation}{propertyName} = {propertySerializer.DynamicInvoke(propertyValue)}");
continue;
}

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.

Опять же это можно сделать отдельной функцией, которая принимает сериалайзер, но она будет принимать очень много параметров, поэтому делать этого я не стал.


if (typeCultures.TryGetValue(propertyType, out var cult))
{
if (propertyValue is IFormattable format)
{
sb.AppendLine($"{identation}{propertyName} = {format.ToString(null, cult)}");
continue;
}
}

if (propertyTrim.TryGetValue(propertyName, out var toTrimLength) && propertyValue is string stringValue)
{
sb.AppendLine($"{identation}{propertyName} = {stringValue.Substring(0, Math.Min(toTrimLength, stringValue.Length))}");
continue;
}

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.

Это конечно можно вынести в отдельную функцию, но тогда она будет принимать очень много параметров, поэтому я не стал выносить.


sb.AppendLine($"{identation}{propertyName} = {PrintToString(propertyValue, nestingLevel + 1)}");
}
return sb.ToString();
}

private string SerializeCollection(ICollection collection, int nestingLevel)
{
var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine("[");

if (collection is IDictionary dictionary)
{
foreach (var key in dictionary.Keys)
{
sb.Append($"{identation}{{{PrintToString(key, nestingLevel)}: " +
$"{PrintToString(dictionary[key], nestingLevel + 1)}}}");

//sb.Append($"{identation}{{{PrintToString(key, nestingLevel)} = ");
//sb.Append(PrintToString(dictionary[key], nestingLevel + 1) + "}; ");
}

Choose a reason for hiding this comment

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

Комментарии удалить

}
else
{
foreach (var value in collection)
{
sb.Append('\t', nestingLevel + 1);
sb.Append(PrintToString(value, nestingLevel + 1));
}
}

sb.AppendLine($"{identation}]");
return sb.ToString();
}

private MemberInfo GetPropertyName<TPropType>(Expression<Func<TOwner, TPropType>> propertySelector)
{
if (propertySelector.Body is MemberExpression memberExpression)
return memberExpression.Member;

if (propertySelector.Body is UnaryExpression unaryExpression && unaryExpression.Operand is MemberExpression operand)
return operand.Member;

throw new ArgumentException("Invalid property selector expression");
}

internal void AddPropertySerializer<TPropType>(string propertyName, Func<TPropType, string> serializer) =>
propertySerializers[propertyName] = serializer;

internal void AddTypeSerializer<TPropType>(Func<TPropType, string> serializer) =>
typeSerializers[typeof(TPropType)] = serializer;

internal void AddStringPropertyTrim(string propertyName, int maxLength) =>
propertyTrim[propertyName] = maxLength;

internal void AddNumericCulture<TNumericCulture>(CultureInfo culture) =>
typeCultures[typeof(TNumericCulture)] = culture;

Choose a reason for hiding this comment

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

А нам правда этот метод нужен? Это не то же самое, что Func<T, string> ? Ну то есть ровным счетом кастомный сериализатор

}
}
33 changes: 33 additions & 0 deletions ObjectPrinting/PrintingConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Reflection;

namespace ObjectPrinting;

public class PrintingConfiguration<TOwner, TPropType>

Choose a reason for hiding this comment

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

А что с названием? Чем PrintingConfig отличается от PrintingConfiguration по имени?

{
private readonly PrintingConfig<TOwner> parentConfig;
private readonly MemberInfo? propertyMemberInfo;

internal PrintingConfig<TOwner> ParentConfig => parentConfig;
internal MemberInfo? PropertyMemberInfo => propertyMemberInfo;

public PrintingConfiguration(PrintingConfig<TOwner> parentConfig, MemberInfo? propertyMemberInfo)
{
this.parentConfig = parentConfig;
this.propertyMemberInfo = propertyMemberInfo;
}

public PrintingConfig<TOwner> Using(Func<TPropType, string> printingMethod)
{
if (propertyMemberInfo == null)
{
parentConfig.AddTypeSerializer(printingMethod);
}
else
{
parentConfig.AddPropertySerializer(propertyMemberInfo.Name, printingMethod);
}

return parentConfig;
}
}
24 changes: 20 additions & 4 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using NUnit.Framework;
using System;
using System.Globalization;
using ObjectPrinting.Extensions;

namespace ObjectPrinting.Tests
{
Expand All @@ -8,20 +11,33 @@ public class ObjectPrinterAcceptanceTests
[Test]
public void Demo()
{
var person = new Person { Name = "Alex", Age = 19 };
//var person = new Person(new Guid(),"Alex",173.65,19);
var person = new Person { Age = 19, Name = "Alex", Height = 173.65, Id = new Guid() };

var printer = ObjectPrinter.For<Person>();
var printer = ObjectPrinter.For<Person>()
//1. Исключить из сериализации свойства определенного типа
.Exclude<Guid>()
//2. Указать альтернативный способ сериализации для определенного типа
.Printing<int>().Using(i => i.ToString("X"))
//3. Для числовых типов указать культуру
.Printing<double>().UseCulture(CultureInfo.InvariantCulture)
//4. Настроить сериализацию конкретного свойства
.Printing(x => x.Name).Using(p => $"---{p}---")
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
.Printing(p => p.Name).TrimmedToLength(10)
//6. Исключить из сериализации конкретного свойства

.Exclude(p => p.Age);

string s1 = printer.PrintToString(person);
Console.WriteLine(s1);

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
var s2 = person.PrintToString();
Console.WriteLine(s2);

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
//8. ...с конфигурированием
var s3 = person.PrintToString(p => p.Exclude(x => x.Age));
Console.WriteLine(s3);
}
}
}
Loading