-
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
Савенко Дмитрий #226
base: master
Are you sure you want to change the base?
Савенко Дмитрий #226
Changes from 1 commit
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,16 @@ | ||
using System; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public static class ObjectPrinterExtensions | ||
{ | ||
public static string PrintToString<T>(this T obj) | ||
{ | ||
return ObjectPrinter.For<T>().PrintToString(obj); | ||
} | ||
|
||
public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> printConfig) | ||
{ | ||
return printConfig(ObjectPrinter.For<T>()).PrintToString(obj); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,171 @@ | ||
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 | ||
namespace ObjectPrinting; | ||
|
||
public class PrintingConfig<TOwner> | ||
{ | ||
public class PrintingConfig<TOwner> | ||
private readonly Type[] primitiveTypes = | ||
{ | ||
typeof(int), | ||
typeof(double), | ||
typeof(float), | ||
typeof(string), | ||
typeof(DateTime), | ||
typeof(TimeSpan), | ||
typeof(Guid) | ||
}; | ||
|
||
private readonly HashSet<Type> excludingTypes = new(); | ||
private readonly HashSet<PropertyInfo> excludingProperties = new(); | ||
|
||
public Dictionary<Type, Func<object, string>> TypeSerializers { get; } = new(); | ||
public Dictionary<Type, CultureInfo> Cultures { get; } = new(); | ||
public Dictionary<PropertyInfo, Func<object, string>> PropertySerializers { get; } = new(); | ||
public Dictionary<PropertyInfo, int> PropertiesMaxLength { get; } = new(); | ||
|
||
public PrintingConfig<TOwner> Excluding<TPropertyType>() | ||
{ | ||
excludingTypes.Add(typeof(TPropertyType)); | ||
return this; | ||
} | ||
|
||
public PrintingConfig<TOwner> Excluding<TProperty>(Expression<Func<TOwner, TProperty>> memberSelector) | ||
{ | ||
excludingProperties.Add(GetProperty(memberSelector)); | ||
return this; | ||
} | ||
|
||
public TypePrintingConfig<TOwner, TPropertyType> For<TPropertyType>() => new(this); | ||
|
||
public PropertyPrintingConfig<TOwner, TProperty> For<TProperty>(Expression<Func<TOwner, TProperty>> memberSelector) | ||
{ | ||
return new PropertyPrintingConfig<TOwner, TProperty>(this, GetProperty(memberSelector)); | ||
} | ||
|
||
public string PrintToString(TOwner obj) | ||
{ | ||
return Serialize(obj, 0, new Dictionary<object, int>()); | ||
} | ||
|
||
private string Serialize(object obj, int nestingLevel, Dictionary<object, int> parsedObjects) | ||
{ | ||
public string PrintToString(TOwner obj) | ||
if (obj is null) | ||
{ | ||
return "null"; | ||
} | ||
|
||
if (primitiveTypes.Contains(obj.GetType())) | ||
{ | ||
return PrintToString(obj, 0); | ||
return obj + Environment.NewLine; | ||
} | ||
|
||
private string PrintToString(object obj, int nestingLevel) | ||
if (parsedObjects.TryGetValue(obj, out var level)) | ||
{ | ||
return $"<circular reference to {obj.GetType().Name} at level {level}>"; | ||
} | ||
|
||
parsedObjects[obj] = nestingLevel; | ||
|
||
switch (obj) | ||
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. |
||
{ | ||
//TODO apply configurations | ||
if (obj == null) | ||
return "null" + Environment.NewLine; | ||
case IDictionary dictionary: | ||
return SerializeDictionary(dictionary, nestingLevel, parsedObjects); | ||
case IEnumerable collection: | ||
return SerializeCollection(collection, nestingLevel, parsedObjects); | ||
default: | ||
return SerializeProperties(obj, nestingLevel, parsedObjects); | ||
} | ||
} | ||
|
||
var finalTypes = new[] | ||
{ | ||
typeof(int), typeof(double), typeof(float), typeof(string), | ||
typeof(DateTime), typeof(TimeSpan) | ||
}; | ||
if (finalTypes.Contains(obj.GetType())) | ||
return obj + Environment.NewLine; | ||
private string SerializeDictionary(IDictionary dictionary, int nestingLevel, Dictionary<object, int> parsedObjects) | ||
{ | ||
var indentation = new string('\t', nestingLevel + 1); | ||
var sb = new StringBuilder("Dictionary {\n"); | ||
|
||
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(); | ||
foreach (DictionaryEntry kvp in dictionary) | ||
{ | ||
var key = Serialize(kvp.Key, nestingLevel + 1, parsedObjects).Trim(); | ||
var value = Serialize(kvp.Value, nestingLevel + 1, parsedObjects).Trim(); | ||
sb.AppendLine($"{indentation}{key} : {value}"); | ||
} | ||
|
||
sb.AppendLine(new string('\t', nestingLevel) + "}"); | ||
return sb.ToString(); | ||
} | ||
|
||
private string SerializeCollection(IEnumerable collection, int nestingLevel, Dictionary<object, int> parsedObjects) | ||
{ | ||
var indentation = new string('\t', nestingLevel + 1); | ||
var sb = new StringBuilder("Collection [\n"); | ||
|
||
foreach (var element in collection) | ||
{ | ||
var value = Serialize(element, nestingLevel + 1, parsedObjects).Trim(); | ||
sb.AppendLine($"{indentation}{value}"); | ||
} | ||
|
||
sb.AppendLine(new string('\t', nestingLevel) + "]"); | ||
return sb.ToString(); | ||
} | ||
|
||
private string SerializeProperties(object obj, int nestingLevel, Dictionary<object, int> parsedObjects) | ||
{ | ||
var type = obj.GetType(); | ||
var sb = new StringBuilder($"{type.Name} {{\n"); | ||
var indentation = new string('\t', nestingLevel + 1); | ||
|
||
foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) | ||
{ | ||
if (excludingProperties.Contains(propertyInfo) | ||
|| excludingTypes.Contains(propertyInfo.PropertyType)) | ||
continue; | ||
|
||
var serializedValue = SerializeProperty(propertyInfo, obj, nestingLevel + 1, parsedObjects); | ||
sb.AppendLine($"{indentation}{propertyInfo.Name} = {serializedValue}"); | ||
} | ||
|
||
sb.AppendLine(new string('\t', nestingLevel) + "}"); | ||
return sb.ToString(); | ||
} | ||
|
||
private string SerializeProperty(PropertyInfo propertyInfo, object obj, int nestingLevel, Dictionary<object, int> parsedObjects) | ||
{ | ||
var propertyValue = propertyInfo.GetValue(obj); | ||
|
||
if (PropertySerializers.TryGetValue(propertyInfo, out var propertySerializer)) | ||
{ | ||
return propertySerializer(propertyValue); | ||
} | ||
|
||
if (TypeSerializers.TryGetValue(propertyInfo.PropertyType, out var typeSerializer)) | ||
{ | ||
return typeSerializer(propertyValue); | ||
} | ||
|
||
if (PropertiesMaxLength.TryGetValue(propertyInfo, out var maxLength) && propertyValue is string str) | ||
{ | ||
return str[..Math.Min(str.Length, maxLength)]; | ||
} | ||
|
||
return Cultures.TryGetValue(propertyInfo.PropertyType, out var culture) | ||
? Convert.ToString(propertyValue, culture) | ||
: Serialize(propertyValue, nestingLevel, parsedObjects); | ||
} | ||
|
||
private PropertyInfo GetProperty<TProperty>(Expression<Func<TOwner, TProperty>> memberSelector) | ||
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. статический метод |
||
{ | ||
if (memberSelector.Body is not MemberExpression memberExpression) | ||
{ | ||
throw new ArgumentException("Expression must refer to a property."); | ||
} | ||
|
||
return memberExpression.Member as PropertyInfo ?? throw new ArgumentException("Expression must refer to a property."); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System; | ||
using System.Reflection; | ||
|
||
namespace ObjectPrinting | ||
{ | ||
public class PropertyPrintingConfig<TOwner, TProperty> | ||
{ | ||
private readonly PrintingConfig<TOwner> _config; | ||
private readonly PropertyInfo _propertyInfo; | ||
|
||
public PropertyPrintingConfig(PrintingConfig<TOwner> config, PropertyInfo propertyInfo) | ||
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. Можно использовать PrimaryConstructor:
|
||
{ | ||
_config = config; | ||
_propertyInfo = propertyInfo; | ||
} | ||
|
||
public PrintingConfig<TOwner> UseSerializer(Func<TProperty, string> serializer) | ||
{ | ||
_config.PropertySerializers[_propertyInfo] = value => serializer((TProperty)value); | ||
return _config; | ||
} | ||
|
||
public PrintingConfig<TOwner> SetMaxLength(int maxLength) | ||
{ | ||
_config.PropertiesMaxLength[_propertyInfo] = maxLength; | ||
return _config; | ||
} | ||
} | ||
} |
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.
Тут надо пометить, что
obj
может бытьnull
:Отсюда же вытекает, что коллекции
TypeSerializers
иPropertySerializers
могут содержатьnullable
типы:Если это всё проделать, то останется одно неочевидное место поведения в методе
SerializeProperty
:Convert.ToString
может вернутьnull
, а не строку - непонятно, как это в итоге отразиться на всей логике.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.
Добавил
Convert.ToString(propertyValue, culture) ?? "null"