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

Зайцев Дмитрий #228

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

namespace ObjectPrinting.Extensions;

public static class PropertyPrintingConfigExtensions
{
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PropertyPrintingConfig<TOwner, string> propConfig,
int maxLen)
{
if (propConfig.Property is null)
throw new ArgumentException("The name of the property is not specified.");

propConfig.ParentConfig.AddLengthProperty(propConfig.Property, maxLen);

return propConfig.ParentConfig;
}
}
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>();
}
}
2 changes: 0 additions & 2 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />

Choose a reason for hiding this comment

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

Тоже лишний пакет

<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
</Project>
189 changes: 189 additions & 0 deletions ObjectPrinting/ObjectSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using System;
using System.Collections;

public class ObjectSerializer<TOwner>
{
private readonly PrintingConfigStorage _config;
private readonly HashSet<object> _processedObjects = new();
private const char tab = '\t';

Choose a reason for hiding this comment

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

Для констант есть гайдлайны именования. Райдер их, кстати, подсказывает)
image


public ObjectSerializer(PrintingConfigStorage config)
{
_config = config;
}

public string Serialize(TOwner obj)
{
_processedObjects.Clear();
return SerializeObject(obj, 0);

Choose a reason for hiding this comment

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

Потенциальное NRE
image

Решить можно по разному, но хорошим решением было бы использовать MaybeNullWhen
Например так
private bool TrySerializeFinalType(object obj, [MaybeNullWhen(false)] out string serializedFinalType)

Рекомендую ознакомиться, очень полезный атрибут)
https://learn.microsoft.com/ru-ru/dotnet/api/system.diagnostics.codeanalysis.maybenullwhenattribute?view=net-8.0

}

private string? SerializeObject(object? obj, int nestingLevel)
{
if (obj == null)
return "null" + Environment.NewLine;

if (obj.GetType().IsValueType || obj is string)
{
return TrySerializeFinalType(obj, out var serializedFinalType)
? serializedFinalType
: obj.ToString() + Environment.NewLine;
}

if (_processedObjects.Contains(obj))

Choose a reason for hiding this comment

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

Стоит уделять внимание подсказкам от IDE, они могут помочь писать код чище)
Например здесь
image

Потому что сигнатура на добавление
image

return "Circular Reference" + Environment.NewLine;

_processedObjects.Add(obj);

if (TrySerializeFinalType(obj, out var serializedFinal))
return serializedFinal!;

if (TrySerializeCollection(obj, nestingLevel, out var serializedCollection))
return serializedCollection!;

return SerializeComplexType(obj, nestingLevel);
}


private bool TrySerializeFinalType(object obj, out string? serializedFinalType)
{
var type = obj.GetType();

Choose a reason for hiding this comment

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

Получение типа дублируется


if (type.IsPrimitive || obj is string || obj is DateTime || obj is TimeSpan || obj is Guid)

Choose a reason for hiding this comment

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

Очень большое и сложное условие. Как минимум полезно вынести в отдельный метод. Но в целом полезнее вообще в отдельный класс, т.к. такое место потенциальная точка расширения логики

{
serializedFinalType = obj switch
{
IFormattable formattable when _config.TypeCultures.TryGetValue(type, out var culture) =>
formattable.ToString(null, culture),
IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
_ => obj.ToString() ?? ""
};

serializedFinalType += Environment.NewLine;
return true;
}

serializedFinalType = null;
return false;
}

private bool TrySerializeCollection(object obj, int nestingLevel, out string? serializedCollection)
{
if (obj is IDictionary dictionary)
{
serializedCollection = SerializeDictionary(dictionary, nestingLevel);
return true;
}

if (obj is not IEnumerable collection)
{
serializedCollection = null;
return false;
}

var builder = new StringBuilder();
var indentation = new string(tab, nestingLevel + 1);

builder.AppendLine(GetCollectionTypeName(obj) + ":");
foreach (var item in collection)
builder.Append(indentation + SerializeObject(item, nestingLevel + 1));

serializedCollection = builder.ToString();
return true;
}

private string SerializeDictionary(IDictionary dictionary, int nestingLevel)
{
var builder = new StringBuilder();
var indentation = new string(tab, nestingLevel + 1);

builder.AppendLine(GetCollectionTypeName(dictionary) + ":");

Choose a reason for hiding this comment

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

Дублируется GetCollectionTypeName(something) + ":"

bool isFirstEntry = true;

Choose a reason for hiding this comment

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

Сильно выбивается стиль. Во всех других местах var, а здесь явный тип. Предлагаю писать код консистентно


foreach (DictionaryEntry entry in dictionary)
{
if (!isFirstEntry)
{
builder.AppendLine();
}

builder.AppendLine(indentation + "KeyValuePair");
builder.AppendLine(indentation + "\tKey = " + SerializeObject(entry.Key, nestingLevel + 2).TrimEnd());

Choose a reason for hiding this comment

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

Дублируется вызов SerializeObject(<something>, nestingLevel + 2).TrimEnd()

builder.Append(indentation + "\tValue = " + SerializeObject(entry.Value, nestingLevel + 2).TrimEnd());

isFirstEntry = false;
}

return builder.ToString();
}



private string SerializeComplexType(object obj, int nestingLevel)
{
var builder = new StringBuilder();
var indentation = new string(tab, nestingLevel + 1);
var type = obj.GetType();

builder.AppendLine(type.Name);

foreach (var property in type.GetProperties())
{
if (_config.ExcludedTypes.Contains(property.PropertyType) ||
_config.ExcludedProperties.Contains(property))
continue;

var value = SerializeProperty(property, obj, nestingLevel+1);
builder.Append(indentation + property.Name + " = " + value);
}

return builder.ToString();
}

private string SerializeProperty(PropertyInfo property, object obj, int nestingLevel)
{
var value = property.GetValue(obj);

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

Choose a reason for hiding this comment

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

Стоит всегда стараться избегать использования null-forgiving-оператора


if (_config.PropertySerializationMethods.TryGetValue(property, out var propertySerializer))
return propertySerializer(value!) + Environment.NewLine;

if (_config.PropertyLengths.TryGetValue(property, out var length))
{
var stringValue = value?.ToString() ?? "";
return stringValue.Substring(0, Math.Min(length, stringValue.Length)) + Environment.NewLine;
}

if (_config.PropertyCultures.TryGetValue(property, out var culture) && value is IFormattable formattable)
return formattable.ToString(null, culture) + Environment.NewLine;

return SerializeObject(value, nestingLevel);
}

static string GetCollectionTypeName(object obj)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));

Choose a reason for hiding this comment

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

А здесь точно возможен null?


var type = obj.GetType();

if (type.IsGenericType)
{
return type.GetGenericTypeDefinition().Name.Split('`')[0];
}
else if (type.IsArray)
{
return $"{type.GetElementType().Name}[]";
}
else
{
return type.Name;
}
}
}

101 changes: 63 additions & 38 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,66 @@
using ObjectPrinting;
using System.Globalization;
using System.Linq.Expressions;
using System;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ObjectPrinting

Choose a reason for hiding this comment

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

Куда-то пропал namespace почти везде
image


public class PrintingConfig<TOwner>

Choose a reason for hiding this comment

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

В этом классе оказалось много ответственности (нарушается SRP): конфигурация объектов для сериализации, непосредственно алгоритм сериализации, а также работа с рефлексией

Copy link
Author

Choose a reason for hiding this comment

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

Разделил

{
public class PrintingConfig<TOwner>
{
public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}

private string PrintToString(object obj, int nestingLevel)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var finalTypes = new[]
{
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())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
}
return sb.ToString();
}
}
}
private readonly PrintingConfigStorage _config = new();

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

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
var propertyName = ReflectionHelper.GetProperty(memberSelector);
return new PropertyPrintingConfig<TOwner, TPropType>(this, propertyName);
}

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

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
var property = ReflectionHelper.GetProperty(memberSelector);
_config.ExcludedProperties.Add(property);
return this;
}

public void SpecifyTheCulture<TType>(CultureInfo culture)
{
_config.TypeCultures[typeof(TType)] = culture;
}

public void SpecifyTheCulture(CultureInfo culture, PropertyInfo property)
{
_config.PropertyCultures[property] = culture;
}

public void AddSerializationMethod<TType>(Func<TType, string> serializationMethod)
{
_config.TypeSerializationMethods[typeof(TType)] = obj => serializationMethod((TType)obj);
}

public void AddSerializationMethod<TType>(Func<TType, string> serializationMethod, PropertyInfo property)
{
_config.PropertySerializationMethods[property] = obj => serializationMethod((TType)obj);
}

public void AddLengthProperty(PropertyInfo property, int trimLength)
{
_config.PropertyLengths[property] = trimLength;
}

public string PrintToString(TOwner obj)
{
var serializer = new ObjectSerializer<TOwner>(_config);
return serializer.Serialize(obj);
}
}
14 changes: 14 additions & 0 deletions ObjectPrinting/PrintingConfigStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System;
using System.Reflection;

public class PrintingConfigStorage
{
public HashSet<Type> ExcludedTypes { get; } = new();
public Dictionary<Type, Func<object, string>> TypeSerializationMethods { get; } = new();
public Dictionary<Type, IFormatProvider> TypeCultures { get; } = new();
public Dictionary<PropertyInfo, IFormatProvider> PropertyCultures { get; } = new();
public Dictionary<PropertyInfo, Func<object, string>> PropertySerializationMethods { get; } = new();
public HashSet<PropertyInfo> ExcludedProperties { get; } = new();
public Dictionary<PropertyInfo, int> PropertyLengths { get; } = new();
}
Loading