-
Notifications
You must be signed in to change notification settings - Fork 244
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
Сазонов Александр #220
base: master
Are you sure you want to change the base?
Сазонов Александр #220
Changes from 12 commits
aebd437
fedd4aa
e64f21e
20a92af
b1b04f5
d7f4172
d79045d
9db8a1d
11a19d8
4363c63
6577434
e6706f8
649c02b
c6f6591
0d75cc2
8e2d725
800c19c
111d4b1
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,20 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace ObjectPrinting.Extensions; | ||
|
||
public static class DictionaryExtensions | ||
{ | ||
public static void AddRange<TKey, TValue>(this Dictionary<TKey, TValue> current, Dictionary<TKey, TValue> toAdd) | ||
where TKey : notnull | ||
{ | ||
foreach (var item in toAdd) | ||
current.Add(item.Key, item.Value); | ||
} | ||
|
||
public static void AddMethod<TKey, TValue>(this Dictionary<TKey, TValue> current, TKey key, TValue value) | ||
where TKey : notnull | ||
{ | ||
if (!current.TryAdd(key, value)) | ||
throw new InvalidOperationException($"Type {typeof(TKey).Name} is already registered"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace ObjectPrinting.Extensions; | ||
|
||
public static class HashsetExtensions | ||
{ | ||
public static void AddRange<T>(this HashSet<T> current, HashSet<T> toAdd) | ||
{ | ||
foreach (var item in toAdd) | ||
current.Add(item); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using System.Reflection; | ||
|
||
namespace ObjectPrinting.Extensions; | ||
|
||
public static class MemberInfoExtensions | ||
{ | ||
public static object? GetValue(this MemberInfo? memberInfo, object obj) => | ||
memberInfo switch | ||
{ | ||
FieldInfo fieldInfo => fieldInfo.GetValue(obj), | ||
PropertyInfo propertyInfo => propertyInfo.GetValue(obj), | ||
_ => null | ||
}; | ||
Comment on lines
+7
to
+13
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. Круто, что сразу обработал и поля, и свойства 🔥 |
||
|
||
public static Type? GetMemberType(this MemberInfo memberInfo) => | ||
memberInfo.MemberType switch | ||
{ | ||
MemberTypes.Constructor => memberInfo.DeclaringType, | ||
MemberTypes.Event => ((EventInfo)memberInfo).EventHandlerType, | ||
MemberTypes.Field => ((FieldInfo)memberInfo).FieldType, | ||
MemberTypes.Method => ((MethodInfo)memberInfo).ReturnType, | ||
MemberTypes.Property => ((PropertyInfo)memberInfo).PropertyType, | ||
MemberTypes.TypeInfo => ((TypeInfo)memberInfo).BaseType, | ||
MemberTypes.Custom => memberInfo.DeclaringType, | ||
MemberTypes.NestedType => memberInfo.DeclaringType, | ||
MemberTypes.All => memberInfo.DeclaringType, | ||
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. А разве нас интересует из этого списка что-то кроме |
||
_ => throw new ArgumentOutOfRangeException(nameof(memberInfo), memberInfo, | ||
"Unsupported member type.") | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace ObjectPrinting.Extensions; | ||
|
||
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 |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using ObjectPrinting.PropertyPrintingConfig; | ||
|
||
namespace ObjectPrinting.Extensions; | ||
|
||
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> Using<TOwner, TPropType>( | ||
this PropertyPrintingConfig<TOwner, TPropType> propConfig, CultureInfo culture, string? format = null) | ||
where TPropType : IFormattable | ||
{ | ||
var parentConfig = ((IPropertyPrintingConfig<TOwner, TPropType>)propConfig).ParentConfig; | ||
|
||
parentConfig.TypeSerializationMethod.AddMethod(typeof(TPropType), (Func<TPropType, string>)FormatFunc); | ||
|
||
return parentConfig; | ||
|
||
string FormatFunc(TPropType obj) => obj.ToString(format, culture); | ||
} | ||
|
||
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(this PropertyPrintingConfig<TOwner, string> propConfig, | ||
int maxLength) | ||
{ | ||
if (maxLength < 1) | ||
throw new ArgumentException($"{nameof(maxLength)} should be greater than 1"); | ||
|
||
var parentConfig = ((IPropertyPrintingConfig<TOwner, string>)propConfig).ParentConfig; | ||
|
||
parentConfig.MemberSerializationMethod.AddMethod(propConfig.PropertyMemberInfo, (Func<string, string>)Function); | ||
|
||
return parentConfig; | ||
|
||
string Function(string str) => | ||
string.IsNullOrEmpty(str) || str.Length <= maxLength | ||
? str | ||
: str[..maxLength]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace ObjectPrinting.Extensions; | ||
|
||
public static class TypeExtensions | ||
{ | ||
private static readonly HashSet<Type> FinalTypes = | ||
[ | ||
typeof(int), | ||
typeof(double), | ||
typeof(float), | ||
typeof(string), | ||
typeof(DateTime), | ||
typeof(TimeSpan), | ||
typeof(Guid), | ||
typeof(DateTimeOffset) | ||
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. А тут точно перечислены все примитивные типы? Ещё забыл всякие uint, ulong, short и тд. Нет ли более простого способа узнать, является ли тип простым, не перечисляя все вариации простых типов? |
||
]; | ||
|
||
public static bool IsFinal(this Type currentType) => | ||
FinalTypes.Contains(currentType); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
global using System; | ||
global using System.Globalization; |
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,139 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using System.Text; | ||
using ObjectPrinting.Extensions; | ||
using ObjectPrinting.PropertyPrintingConfig; | ||
|
||
namespace ObjectPrinting | ||
namespace ObjectPrinting; | ||
|
||
public class PrintingConfig<TOwner> | ||
{ | ||
public class PrintingConfig<TOwner> | ||
private readonly HashSet<object> seenObjects = []; | ||
private readonly HashSet<Type> excludedProperties = []; | ||
private readonly HashSet<MemberInfo> excludedMembers = []; | ||
internal readonly Dictionary<Type, Delegate> TypeSerializationMethod = new(); | ||
internal readonly Dictionary<MemberInfo, Delegate> MemberSerializationMethod = new(); | ||
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. Кажется, что можно взять более точные типы вместо 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. Круто, что сделал сразу |
||
|
||
public PrintingConfig(PrintingConfig<TOwner>? parent = null) | ||
{ | ||
if (parent == null) | ||
return; | ||
|
||
excludedProperties.AddRange(parent.excludedProperties); | ||
excludedMembers.AddRange(parent.excludedMembers); | ||
TypeSerializationMethod.AddRange(parent.TypeSerializationMethod); | ||
MemberSerializationMethod.AddRange(parent.MemberSerializationMethod); | ||
} | ||
|
||
public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>() | ||
{ | ||
var configCopy = new PrintingConfig<TOwner>(this); | ||
return new PropertyPrintingConfig<TOwner, TPropType>(configCopy); | ||
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. А почему просто не прокидываешь |
||
} | ||
|
||
public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>( | ||
Expression<Func<TOwner, TPropType>> memberSelector) | ||
{ | ||
var configCopy = new PrintingConfig<TOwner>(this); | ||
var memberInfo = GetMemberInfo(memberSelector); | ||
return new PropertyPrintingConfig<TOwner, TPropType>(configCopy, memberInfo); | ||
} | ||
|
||
private static MemberInfo? GetMemberInfo<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector) => | ||
(memberSelector.Body as MemberExpression)?.Member; | ||
|
||
public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector) | ||
{ | ||
var configClone = new PrintingConfig<TOwner>(this); | ||
var memberInfo = GetMemberInfo(memberSelector); | ||
if (memberInfo is null) | ||
throw new ArgumentException("Invalid member selector."); | ||
configClone.excludedMembers.Add(memberInfo); | ||
return configClone; | ||
} | ||
|
||
public PrintingConfig<TOwner> Excluding<TPropType>() | ||
{ | ||
var configCopy = new PrintingConfig<TOwner>(this); | ||
configCopy.excludedProperties.Add(typeof(TPropType)); | ||
return configCopy; | ||
} | ||
|
||
public string PrintToString(TOwner owner) => | ||
PrintToString(owner, 0); | ||
|
||
private string PrintToString(object? obj, int nestingLevel) | ||
{ | ||
if (obj == null) | ||
return "null" + Environment.NewLine; | ||
|
||
if (seenObjects.Contains(obj)) | ||
return "recursive reference" + Environment.NewLine; | ||
Comment on lines
+55
to
+56
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. Смотри, у тебя сейчас в свойствах класса образовалось два типа свойств:
|
||
|
||
var type = obj.GetType(); | ||
|
||
if (type.IsFinal()) | ||
return obj + Environment.NewLine; | ||
|
||
var numberOfTabs = new string('\t', nestingLevel + 1); | ||
var sb = new StringBuilder(); | ||
sb.AppendLine(type.Name); | ||
if (obj is IEnumerable enumerable) | ||
sb.Append(GetCollectionString(enumerable, numberOfTabs, nestingLevel)); | ||
else | ||
sb.Append(GetSingleElementString(obj, numberOfTabs, nestingLevel)); | ||
Comment on lines
+66
to
+69
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. К слову о надежности тестов, попробовал засериализовать словарь Получил такое значение (ещё с decimal и byte что-то, но это скорее всего от того, что ты их не добавил в FinalTypes)
|
||
|
||
return sb.ToString(); | ||
} | ||
|
||
private string GetCollectionString(IEnumerable enumerable, string numberOfTabs, int nestingLevel) | ||
{ | ||
public string PrintToString(TOwner obj) | ||
var sb = new StringBuilder(); | ||
sb.AppendLine("["); | ||
foreach (var el in enumerable) | ||
{ | ||
return PrintToString(obj, 0); | ||
var value = PrintToString(el, nestingLevel + 1); | ||
sb.Append($"{numberOfTabs}{value}"); | ||
} | ||
|
||
private string PrintToString(object obj, int nestingLevel) | ||
seenObjects.Add(enumerable); | ||
sb.AppendLine("]"); | ||
return sb.ToString(); | ||
} | ||
|
||
private string GetSingleElementString(object obj, string numberOfTabs, int nestingLevel) | ||
{ | ||
var propertiesAndFields = obj | ||
.GetType() | ||
.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public) | ||
.Where(member => member is PropertyInfo or FieldInfo && !IsMemberExcluded(member)); | ||
var sb = new StringBuilder(); | ||
foreach (var memberInfo in propertiesAndFields) | ||
{ | ||
//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(); | ||
sb.Append(numberOfTabs + memberInfo.Name + " = " + | ||
PrintToString(GetValue(obj, memberInfo), | ||
nestingLevel + 1)); | ||
} | ||
|
||
seenObjects.Add(obj); | ||
return sb.ToString(); | ||
} | ||
|
||
private bool IsMemberExcluded(MemberInfo member) => | ||
excludedProperties.Contains(member.GetMemberType()!) || | ||
excludedMembers.Contains(member); | ||
|
||
private object? GetValue(object obj, MemberInfo memberInfo) | ||
{ | ||
var val = memberInfo.GetValue(obj); | ||
var memberType = memberInfo.GetMemberType(); | ||
return MemberSerializationMethod.TryGetValue(memberInfo, out var memberFunc) | ||
? memberFunc.DynamicInvoke(val) | ||
: TypeSerializationMethod.TryGetValue(memberType!, out var typeFunc) | ||
? typeFunc.DynamicInvoke(val) | ||
: val; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace ObjectPrinting.PropertyPrintingConfig; | ||
|
||
|
||
public interface IPropertyPrintingConfig<TOwner, TPropType> | ||
{ | ||
PrintingConfig<TOwner> ParentConfig { get; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System.Reflection; | ||
using ObjectPrinting.Extensions; | ||
|
||
namespace ObjectPrinting.PropertyPrintingConfig; | ||
|
||
public class PropertyPrintingConfig<TOwner, TPropType>( | ||
PrintingConfig<TOwner> printingConfig, | ||
MemberInfo? memberInfo = null) | ||
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. Тут что-то странное вышло: говорим в конструкторе, что можем принимать null, но при этом позже при обращении к |
||
: IPropertyPrintingConfig<TOwner, TPropType> | ||
{ | ||
public MemberInfo PropertyMemberInfo => | ||
memberInfo != null ? memberInfo : throw new InvalidOperationException("MemberInfo is null."); | ||
|
||
public PrintingConfig<TOwner> Using(Func<TPropType, string> print) | ||
{ | ||
if (memberInfo is null) | ||
printingConfig.TypeSerializationMethod.AddMethod(typeof(TPropType), print); | ||
else | ||
printingConfig.MemberSerializationMethod.AddMethod(memberInfo, print); | ||
|
||
return printingConfig; | ||
} | ||
|
||
PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig; | ||
} |
This file was deleted.
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.
Можно убрать этот экстеншн, словарь кинет примерно такую же ошибку, если попытаешься добавить в него ключ, который уже есть