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

Козлов Данил #185

Open
wants to merge 6 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
28 changes: 28 additions & 0 deletions ObjectPrinting/MemberInfoExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Reflection;

namespace ObjectPrinting
{
public static class MemberInfoExtension
Copy link

Choose a reason for hiding this comment

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

internal, это ведь твоя внутрянка

{
public static object GetMemberValue(this MemberInfo memberInfo, object obj)
{
return memberInfo switch
{
PropertyInfo propertyInfo => propertyInfo.GetValue(obj),
FieldInfo fieldInfo => fieldInfo.GetValue(obj),
_ => throw new InvalidOperationException()
};
}

public static Type GetMemberType(this MemberInfo memberInfo)
{
return memberInfo switch
{
PropertyInfo propertyInfo => propertyInfo.PropertyType,
FieldInfo fieldInfo => fieldInfo.FieldType,
_ => throw new InvalidOperationException()
};
}
}
}
20 changes: 11 additions & 9 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>8</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<PropertyGroup>
<LangVersion>9.0</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1"/>
<PackageReference Include="NUnit" Version="3.12.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
</ItemGroup>

</Project>
187 changes: 168 additions & 19 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,190 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
private static readonly HashSet<Type> _finalTypes = new()
{
typeof(int), typeof(uint), typeof(double), typeof(float), typeof(decimal),
typeof(long), typeof(ulong), typeof(short), typeof(ushort),
typeof(string), typeof(bool),
typeof(DateTime), typeof(TimeSpan), typeof(Guid)
};

private readonly HashSet<MemberInfo> excludedProperties = new();
private readonly HashSet<Type> excludedTypes = new();
protected readonly Dictionary<MemberInfo, int> propertiesMaxLength = new();
protected readonly Dictionary<MemberInfo, Func<object, string>> propertiesSerialization = new();
protected readonly Dictionary<Type, CultureInfo> typesCulture = new();
protected readonly Dictionary<Type, Func<object, string>> typesSerialization = new();
private Func<dynamic> recursivePropertiesSerialization;

public PrintingConfig()
{
}

protected PrintingConfig(PrintingConfig<TOwner> config)
{
typesCulture = config.typesCulture;
propertiesMaxLength = config.propertiesMaxLength;
propertiesSerialization = config.propertiesSerialization;
typesSerialization = config.typesSerialization;
}

public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
return SerializeObject(obj, ImmutableList<object>.Empty);
}

public PrintingConfig<TOwner> Excluding<TPropType>()
{
excludedTypes.Add(typeof(TPropType));

return this;
}

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> member)
{
var memberInfo = GetMember(member);

excludedProperties.Add(memberInfo);

return this;
}

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

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> printMember)
{
var memberInfo = GetMember(printMember);

return new PropertyPrintingConfig<TOwner, TPropType>(this, memberInfo);
}

public PrintingConfig<TOwner> WithCyclicLinkException()
{
recursivePropertiesSerialization = () => throw new ArgumentException("recursive property");

return this;
}

public PrintingConfig<TOwner> WithCyclicLinkMessage(Func<string> printProperty)
{
recursivePropertiesSerialization = printProperty;

return this;
}

public PrintingConfig<TOwner> WithCyclicLinkIgnored()
{
recursivePropertiesSerialization = () => string.Empty;

return this;
}

private string PrintToString(object obj, int nestingLevel)
private string SerializeObject(object obj, IImmutableList<object> previousObjects)
Copy link

Choose a reason for hiding this comment

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

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

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

var finalTypes = new[]
var type = obj.GetType();
if (previousObjects.Any(prev => prev == obj))
{
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();
return recursivePropertiesSerialization != null
? (string)recursivePropertiesSerialization() : "Recursive property";
}

if (obj is IEnumerable && obj is not string)
return SerializeEnumerable(obj, previousObjects);

if (_finalTypes.Contains(type))
return SerializeFinalType(obj);

var indentation = new string('\t', previousObjects.Count + 1);
var objectSerialization = new StringBuilder().AppendLine(type.Name);
previousObjects = previousObjects.Add(obj);
var members = Array.Empty<MemberInfo>().Concat(type.GetProperties()).Concat(type.GetFields());
foreach (var memberInfo in members)
{
if (excludedProperties.Contains(memberInfo)
|| excludedTypes.Contains(memberInfo.GetMemberType()))
continue;

objectSerialization.Append(indentation);
objectSerialization.Append(memberInfo.Name);
objectSerialization.Append(" = ");
objectSerialization.Append(SerializeMember(memberInfo, obj, previousObjects));
objectSerialization.Append(Environment.NewLine);
}

return objectSerialization.ToString();
}

private string SerializeFinalType(object obj)
{
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
if (typesSerialization.TryGetValue(type, out var serialization))
return serialization.Invoke(obj);

var result = obj.ToString();
if (typesCulture.ContainsKey(obj.GetType()))
result = ((IFormattable)obj).ToString(null, typesCulture[type]);

return result;
}

private string SerializeMember(MemberInfo memberInfo, object obj, IImmutableList<object> previousObjects)
{
var value = memberInfo.GetMemberValue(obj);
var result = propertiesSerialization.TryGetValue(memberInfo, out var serialization)
? serialization.Invoke(value)
: SerializeObject(value, previousObjects).TrimEnd();

var propertyMaxLength = propertiesMaxLength.TryGetValue(memberInfo, out var length)
? length
: result.Length;

result = result[..propertyMaxLength];

return result;
}

private string SerializeEnumerable(object obj, IImmutableList<object> previousObjects)
{
var enumerable = (IEnumerable)obj;
var serializedEnumerable = new StringBuilder();
var indentation = previousObjects.Count == 0
? string.Empty
: Environment.NewLine + new string('\t', previousObjects.Count + 1);

foreach (var item in enumerable)
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
serializedEnumerable.Append(indentation);
serializedEnumerable.Append(SerializeObject(item, previousObjects));
}
return sb.ToString();

return serializedEnumerable.ToString();
}

private static MemberInfo GetMember<TPropType>(Expression<Func<TOwner, TPropType>> expression)
{
var memberBody = (MemberExpression)expression.Body;
var memberInfo = memberBody.Member;

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

namespace ObjectPrinting
{
public class PropertyPrintingConfig<TOwner, TPropType> : PrintingConfig<TOwner>
{
private readonly MemberInfo propertyInfo;

public PropertyPrintingConfig(PrintingConfig<TOwner> config, MemberInfo propertyInfo) : base(config)
{
this.propertyInfo = propertyInfo;
}

public PropertyPrintingConfig<TOwner, TPropType> Using(Func<dynamic, string> printProperty)
{
propertiesSerialization.TryAdd(propertyInfo, printProperty);
Copy link

Choose a reason for hiding this comment

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

Не совсем понял, почему именно Try, может затирать?


return this;
}

public PropertyPrintingConfig<TOwner, TPropType> TrimToLength(int length)
{
ArgumentOutOfRangeException.ThrowIfNegative(length);

propertiesMaxLength.Add(propertyInfo, length);

return this;
}
}
}
5 changes: 2 additions & 3 deletions ObjectPrinting/Solved/PrintingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>()
return new PropertyPrintingConfig<TOwner, TPropType>(this);
}

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector)
{
return new PropertyPrintingConfig<TOwner, TPropType>(this);
}
Expand Down Expand Up @@ -51,11 +52,9 @@ private string PrintToString(object obj, int nestingLevel)
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));
}
return sb.ToString();
}
}
Expand Down
4 changes: 2 additions & 2 deletions ObjectPrinting/Solved/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public PropertyPrintingConfig(PrintingConfig<TOwner> printingConfig)
this.printingConfig = printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
return printingConfig;
Expand All @@ -21,8 +23,6 @@ public PrintingConfig<TOwner> Using(CultureInfo culture)
{
return printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
}

public interface IPropertyPrintingConfig<TOwner, TPropType>
Expand Down
4 changes: 2 additions & 2 deletions ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, Printi
return config(ObjectPrinter.For<T>()).PrintToString(obj);
}

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
return ((IPropertyPrintingConfig<TOwner, string>)propConfig).ParentConfig;
}

}
}
10 changes: 5 additions & 5 deletions ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ public void Demo()
//6. Исключить из сериализации конкретного свойства
.Excluding(p => p.Age);

string s1 = printer.PrintToString(person);
var s1 = printer.PrintToString(person);

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

//8. ...с конфигурированием
string s3 = person.PrintToString(s => s.Excluding(p => p.Age));
var s3 = person.PrintToString(s => s.Excluding(p => p.Age));
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.WriteLine(s3);
Expand Down
16 changes: 16 additions & 0 deletions ObjectPrinting/Tests/File.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;

namespace ObjectPrinting.Tests
{
public class File
{
public string field;
public string Name { get; set; }

public Dictionary<string, string> Attributes { get; set; }

public List<string> SimilarNames { get; set; }

public string[] Copies { get; set; }
}
}
Loading