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

Яценко Ирина #198

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
128 changes: 122 additions & 6 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,156 @@
using System;
Copy link

Choose a reason for hiding this comment

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

Все комменты обсуждаемы - можем обсуждать или в тредах, или в личке в телеге
За количество комментов не парься - это нормально, процесс обучения, повышения скилла, ревью именно для этого и нужно

На пофикшенные комментарии можно ставить реакции или писать пометочки, что и почему

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>

This comment was marked as resolved.

This comment was marked as resolved.

{
private readonly List<MemberInfo> exludedProperties = new List<MemberInfo>();

This comment was marked as resolved.

private readonly List<Type> excludedTypes = new List<Type>();
private readonly Dictionary<object, int> complexObjectLinks = new Dictionary<object, int>();
private int maxRecursion = 2;

private readonly Type[] finalTypes =

This comment was marked as resolved.

{
typeof(int), typeof(double), typeof(float), typeof(string), typeof(DateTime), typeof(TimeSpan), typeof(Guid)
};

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

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


public string PrintToString(TOwner obj)

This comment was marked as resolved.

{
return PrintToString(obj, 0);
}

public PrintingConfig<TOwner> WithMaxRecursion(int maxRecursion)
{
if (maxRecursion < 1)
throw new ArgumentException();
this.maxRecursion = maxRecursion;

return this;
}

public PrintingConfig<TOwner> Exclude<TResult>(Expression<Func<TOwner, TResult>> exclude)

This comment was marked as resolved.

{
var memberInfo = GetMemberInfo(exclude);

exludedProperties.Add(memberInfo);

return this;
}

public PrintingConfig<TOwner> SetCulture<T>(CultureInfo culture)
where T : IFormattable
{
return SerializeWith<T>(
p => p.ToString(null, culture));
}

public PrintingConfig<TOwner> SerializeWith<T>(Func<T, string> serialize)

This comment was marked as resolved.

{
Func<object, string> func = p => serialize((T)p);
Copy link

Choose a reason for hiding this comment

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

Можно выспользоваться делегатами и поздней привязкой, это позволит не использовать страшноватый p => serialize((T)p)

Copy link

Choose a reason for hiding this comment

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

Это в сторону dynamic


typeSerializesInfos[typeof(T)] = func;

return this;
}

public PrintingConfig<TOwner> SerializeWith<T>(

This comment was marked as resolved.

This comment was marked as resolved.

Expression<Func<TOwner, T>> property,
Func<T, string> serialize)
{
var memberInfo = GetMemberInfo(property);
Func<object, string> func = p => serialize((T)p);

membersSerializesInfos[memberInfo] = func;

return this;
}

private MemberInfo GetMemberInfo<T>(Expression<Func<TOwner, T>> expression)

This comment was marked as resolved.

{
var memberExpression = expression.Body is UnaryExpression unaryExpression
? (MemberExpression)unaryExpression.Operand
: (MemberExpression)expression.Body;

return memberExpression.Member;
}

public PrintingConfig<TOwner> Trim(Expression<Func<TOwner, string>> property, int length)

This comment was marked as resolved.

This comment was marked as resolved.

{
Func<string, string> func = value => value.Length <= length
? value
: value.Substring(0, length);

This comment was marked as resolved.


return SerializeWith(property, func);
}

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

return this;
}

private string PrintToString(object obj, int nestingLevel)

This comment was marked as resolved.

This comment was marked as resolved.

{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

This comment was marked as resolved.


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

if (!complexObjectLinks.ContainsKey(obj))

This comment was marked as resolved.

This comment was marked as resolved.

complexObjectLinks[obj] = 0;
complexObjectLinks[obj]++;

if (complexObjectLinks[obj] == maxRecursion)
return "Maximum recursion has been reached" + Environment.NewLine;

This comment was marked as resolved.


var identation = new string('\t', nestingLevel + 1);

This comment was marked as resolved.

Copy link

@gisinka gisinka Dec 25, 2023

Choose a reason for hiding this comment

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

Го заюзаем ради интереса string.Intern(), это позволит избежать лишних аллокаций

Если хранить в ста или, что еще хуже, в миллионе строковых переменных одинаковое значение получится, что память для хранения значений строк будет выделяться снова и снова. Интернирование строки это способ обойти эту проблему


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

foreach (var propertyInfo in type.GetProperties())

This comment was marked as resolved.

{
if (exludedProperties.Any(m => m.Name == propertyInfo.Name) ||
excludedTypes.Contains(propertyInfo.PropertyType))
continue;

if (membersSerializesInfos.TryGetValue(propertyInfo, out var serializeMember))
{
var t = serializeMember(propertyInfo.GetValue(obj));

This comment was marked as resolved.


sb.Append(identation + propertyInfo.Name + " = " + t + Environment.NewLine);

This comment was marked as resolved.

continue;
}

if (typeSerializesInfos.TryGetValue(propertyInfo.PropertyType, out var serializeType))
{
var t = serializeType(propertyInfo.GetValue(obj));

sb.Append(identation + propertyInfo.Name + " = " + t + Environment.NewLine);
continue;
}

sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
}

return sb.ToString();
}
}
Expand Down
27 changes: 0 additions & 27 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs

This file was deleted.

104 changes: 104 additions & 0 deletions ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using ObjectPrinting;
using ObjectPrinting.Tests;

namespace ObjectPrintingTests
{
[TestFixture]
public class ObjectPrinterAcceptanceTests

This comment was marked as resolved.

This comment was marked as resolved.

{
[Test]
public void WhenExcludeType_ShouldReturnWithoutThisType()

This comment was marked as resolved.

{
var person = new Person { Name = "Alex", Age = 19 };

var printer = ObjectPrinter.For<Person>();
printer.Exclude(p => p.Age)
.Exclude<double>();

//1. Исключить из сериализации свойства определенного типа
//2. Указать альтернативный способ сериализации для определенного типа
//3. Для числовых типов указать культуру
//4. Настроить сериализацию конкретного свойства
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
//6. Исключить из сериализации конкретного свойства

var s1 = printer.PrintToString(person);
s1.Should().Be(
"Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tSubPerson = null\r\n");

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
//8. ...с конфигурированием
}

[Test]
public void TrimString_ShouldReturnSubstring()

This comment was marked as resolved.

{
var person = new Person { Name = "Petr", Age = 20, Height = 180 };
var printer = ObjectPrinter.For<Person>();

var s1 = printer.Trim(p => p.Name, 1).PrintToString(person);

s1.Should().Be(
"Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = P\r\n\tHeight = 180\r\n\tAge = 20\r\n\tSubPerson = null\r\n");
}

[Test]
public void ShouldSerializeMember_WithGivenFunc()
{
var person = new Person { Name = "Petr", Age = 20, Height = 180 };
var printer = ObjectPrinter.For<Person>();

var s1 = printer.SerializeWith(person => person.Age, age => (age + 1000).ToString()).PrintToString(person);

s1.Should().Be(
"Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Petr\r\n\tHeight = 180\r\n\tAge = 1020\r\n\tSubPerson = null\r\n");
}

[Test]
public void SetCulture_ShouldAddedCultureInfo()
{
var person = new Person { Name = "Petr", Age = 20, Height = 180.5 };
var printer = ObjectPrinter.For<Person>();

var s1 = printer.SetCulture<double>(CultureInfo.InvariantCulture).PrintToString(person);

s1.Should().Be(
"Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Petr\r\n\tHeight = 180.5\r\n\tAge = 20\r\n\tSubPerson = null\r\n");
}

[Test]
public void WhenCyclicLinksWasFound_ShouldPrintWithRecursionLimit()
{
var person = new Person { Name = "Petr", Age = 20, Height = 180, SubPerson = new SubPerson() };
person.SubPerson.Age = 15;
person.SubPerson.Person = person;
var printer = ObjectPrinter.For<Person>();

var s1 = printer.PrintToString(person);

s1.Should().Be(
"Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Petr\r\n\tHeight = 180\r\n\tAge = 20\r\n\tSubPerson = SubPerson\r\n\t\tPerson = Maximum recursion has been reached\r\n\t\tAge = 15\r\n");
}

[Test]
public void WhenPassNull_ShouldReturnNullString()
{
var printer = ObjectPrinter.For<Person>();
var s1 = printer.PrintToString(null);

s1.Should().Be("null\r\n");
}

[Test]
public void WhenPassFinalType_ShouldReturnStringRepresentationOfThisType()
{
var printer = ObjectPrinter.For<int>();
var s1 = printer.PrintToString(1);

s1.Should().Be("1\r\n");
}
}
}
22 changes: 22 additions & 0 deletions ObjectPrintingTests/ObjectPrintingTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ObjectPrinting\ObjectPrinting.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class Person
public string Name { get; set; }
public double Height { get; set; }
public int Age { get; set; }
public SubPerson SubPerson { get; set; }
}
}
11 changes: 11 additions & 0 deletions ObjectPrintingTests/SubPerson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace ObjectPrinting.Tests
{
public class SubPerson
{
public Person Person { get; set; }

public int Age { get; set; }
}
}
21 changes: 15 additions & 6 deletions fluent-api.sln
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{6D308E4A-CEC7-4536-9B87-81CD337A87AD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping", "Samples\FluentMapper\FluentMapping.csproj", "{FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentMapping", "Samples\FluentMapper\FluentMapping.csproj", "{FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samples\FluentMapper.Tests\FluentMapping.Tests.csproj", "{8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentMapping.Tests", "Samples\FluentMapper.Tests\FluentMapping.Tests.csproj", "{8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrintingTests", "ObjectPrintingTests\ObjectPrintingTests.csproj", "{D4C55796-285B-43A6-B0C8-CCAFD9311A7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -35,6 +37,10 @@ Global
{EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU
{D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4C55796-285B-43A6-B0C8-CCAFD9311A7E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -44,4 +50,7 @@ Global
{8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD}
{EFA9335C-411B-4597-B0B6-5438D1AE04C3} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A5E3DAB2-51C4-45F2-8C29-BD9CE58F6479}
EndGlobalSection
EndGlobal