-
Notifications
You must be signed in to change notification settings - Fork 245
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
base: master
Are you sure you want to change the base?
Шестопалов Андрей #219
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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(); | ||
} | ||
} |
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(); | ||
} | ||
} |
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; | ||
} | ||
} |
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; } | ||
} |
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); | ||
} | ||
} |
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>(); | ||
} | ||
} |
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(); | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Получается для каждого типа будем свой отдельный метод добавлять? Можно завести отдельно TypeSerializer для каждого типа, и отдельные реализации для каждого поддерживаемого типа. Тогда получим несколько небольших классов, и у каждого будет своя понятная и небольшая зона ответственности. Кажется тогда этот класс будет не таким раздутым There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
} | ||
} |
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; | ||
} |
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; } | ||
} |
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; } | ||
} | ||
} |
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; } | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Давай по смыслу сгруппируем поля. typeSerializers с propertySerializers например, и excludingTypes с excludingProperties