-
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
Бочаров Александр #222
base: master
Are you sure you want to change the base?
Бочаров Александр #222
Changes from all commits
611a707
9759827
4a8b70d
5bb738f
1e0767a
a5c8873
9f09fc6
9876333
cd8dcfc
fe94dd9
f41c3af
c9c9920
729f353
0d5b655
c8b394e
6d1f9a2
eb59b10
82521df
cf8a079
a0238da
f7c5cb4
cb47a5d
ef8a664
7ca11ca
5d1b557
7377eda
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,30 @@ | ||
using System; | ||
using System.Reflection; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public class MemberPrintingConfig<TOwner, TMemberType>( | ||
PrintingConfig<TOwner> printingConfig, MemberInfo? memberInfo = null) | ||
: IMemberPrintingConfig<TOwner> | ||
{ | ||
internal readonly PrintingConfig<TOwner> PrintingConfig = printingConfig; | ||
internal readonly MemberInfo? MemberInfo = memberInfo; | ||
|
||
public PrintingConfig<TOwner> Using(Func<TMemberType, string> printingMethod) | ||
{ | ||
var resultDelegate = new Func<object, string>(obj => printingMethod((TMemberType)obj)); | ||
if (MemberInfo is null) | ||
PrintingConfig.CustomTypeSerializers[typeof(TMemberType)] = resultDelegate; | ||
else | ||
PrintingConfig.CustomMemberSerializers[MemberInfo] = resultDelegate; | ||
|
||
return PrintingConfig; | ||
} | ||
|
||
PrintingConfig<TOwner> IMemberPrintingConfig<TOwner>.ParentConfig => PrintingConfig; | ||
} | ||
|
||
public interface IMemberPrintingConfig<TOwner> | ||
{ | ||
PrintingConfig<TOwner> ParentConfig { get; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System; | ||
using System.Globalization; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public static class MemberPrintingConfigExtensions | ||
{ | ||
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 MemberPrintingConfig<TOwner, string> memberConfig, int maxLen) | ||
{ | ||
if (maxLen < 0) | ||
throw new ArgumentException("Length to trim must be non negative"); | ||
|
||
var memberInfo = memberConfig.MemberInfo; | ||
|
||
if (memberInfo == null) | ||
memberConfig.PrintingConfig.TrimStringLength = maxLen; | ||
else | ||
memberConfig.PrintingConfig.TrimmedMembers[memberInfo] = maxLen; | ||
|
||
return memberConfig.PrintingConfig; | ||
} | ||
|
||
public static PrintingConfig<TOwner> WithCulture<TOwner, TMemberType>( | ||
this MemberPrintingConfig<TOwner, TMemberType> memberConfig, CultureInfo culture) | ||
where TMemberType : IFormattable | ||
{ | ||
memberConfig.PrintingConfig.CulturesForTypes[typeof(TMemberType)] = culture; | ||
|
||
return memberConfig.PrintingConfig; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace ObjectPrinting; | ||
|
||
public static class ObjectExtensions | ||
{ | ||
public static string PrintToString<T>(this T? obj) | ||
=> ObjectPrinter.For<T>().PrintToString(obj); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,6 @@ | ||
namespace ObjectPrinting | ||
namespace ObjectPrinting; | ||
|
||
public static class ObjectPrinter | ||
{ | ||
public class ObjectPrinter | ||
{ | ||
public static PrintingConfig<T> For<T>() | ||
{ | ||
return new PrintingConfig<T>(); | ||
} | ||
} | ||
public static PrintingConfig<T> For<T>() => new(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,71 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
namespace ObjectPrinting | ||
namespace ObjectPrinting; | ||
|
||
public class PrintingConfig<TOwner> | ||
{ | ||
public class PrintingConfig<TOwner> | ||
{ | ||
public string PrintToString(TOwner obj) | ||
{ | ||
return PrintToString(obj, 0); | ||
} | ||
internal readonly HashSet<Type> ExcludedTypes = []; | ||
internal readonly HashSet<MemberInfo?> ExcludedMembers = []; | ||
|
||
private string PrintToString(object obj, int nestingLevel) | ||
internal Dictionary<MemberInfo, int> TrimmedMembers { get; } = new(); | ||
internal Dictionary<Type, CultureInfo> CulturesForTypes { get; } = new(); | ||
internal Dictionary<Type, Func<object, string>> CustomTypeSerializers { get; } = new(); | ||
internal Dictionary<MemberInfo, Func<object, string>> CustomMemberSerializers { get; } = new(); | ||
|
||
internal int? TrimStringLength; | ||
private int maxNestingLevel = 5; | ||
|
||
public int MaxNestingLevel | ||
{ | ||
get => maxNestingLevel; | ||
private set | ||
{ | ||
//TODO apply configurations | ||
if (obj == null) | ||
return "null" + Environment.NewLine; | ||
|
||
var finalTypes = new[] | ||
if (value <= 0) | ||
{ | ||
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)); | ||
throw new ArgumentOutOfRangeException( | ||
nameof(value), value, "The maxNestingDepth value must be positive."); | ||
} | ||
return sb.ToString(); | ||
maxNestingLevel = value; | ||
} | ||
} | ||
|
||
public MemberPrintingConfig<TOwner, TMemberType> SetPrintingFor<TMemberType>() => new(this); | ||
|
||
public MemberPrintingConfig<TOwner, TMemberType> SetPrintingFor<TMemberType>( | ||
Expression<Func<TOwner, TMemberType>> memberSelector) | ||
{ | ||
var expression = (MemberExpression)memberSelector.Body; | ||
return new MemberPrintingConfig<TOwner, TMemberType>(this, expression.Member); | ||
} | ||
|
||
public PrintingConfig<TOwner> Exclude<TMemberType>(Expression<Func<TOwner, TMemberType>> memberSelector) | ||
{ | ||
var expression = (MemberExpression)memberSelector.Body; | ||
ExcludedMembers.Add(expression.Member); | ||
|
||
return this; | ||
} | ||
|
||
public PrintingConfig<TOwner> Exclude<TMemberType>() | ||
{ | ||
ExcludedTypes.Add(typeof(TMemberType)); | ||
return this; | ||
} | ||
|
||
public PrintingConfig<TOwner> SetSerializationDepth(int depth) | ||
{ | ||
MaxNestingLevel = depth; | ||
return this; | ||
} | ||
|
||
public string PrintToString(TOwner? obj) | ||
{ | ||
var serializer = new Serializer<TOwner>(this); | ||
|
||
return serializer.SerializeObject(obj); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Text; | ||
|
||
namespace ObjectPrinting; | ||
|
||
public class Serializer<TOwner>(PrintingConfig<TOwner> config) | ||
{ | ||
public string SerializeObject(TOwner? obj) | ||
{ | ||
return PrintToString(obj, 0); | ||
} | ||
|
||
private string PrintToString(object? obj, int nestingLevel) | ||
{ | ||
if (nestingLevel > config.MaxNestingLevel) | ||
{ | ||
return $"The MaxNestingLevel of serialization was reached - {config.MaxNestingLevel}" + Environment.NewLine; | ||
} | ||
|
||
if (obj == null) | ||
{ | ||
return "null" + Environment.NewLine; | ||
} | ||
|
||
var indentation = GetIndentation(nestingLevel); | ||
|
||
if (config.CulturesForTypes.TryGetValue(obj.GetType(), out var culture)) | ||
return ((IFormattable)obj).ToString(null, culture) + Environment.NewLine; | ||
|
||
if (IsTypeFinal(obj.GetType())) | ||
{ | ||
var trimLen = config.TrimStringLength; | ||
var printObj = obj.ToString(); | ||
if (obj is string && trimLen != null) | ||
printObj = printObj?.Length >= trimLen ? printObj[..(int)trimLen] : printObj; | ||
return printObj + Environment.NewLine; | ||
} | ||
|
||
var sb = new StringBuilder(); | ||
var type = obj.GetType(); | ||
sb.AppendLine(type.Name); | ||
|
||
if (obj is IEnumerable enumerable) | ||
sb.Append(GetPrintedCollection(enumerable, nestingLevel)); | ||
else | ||
foreach (var memberInfo in type.GetMembers().Where(IsPropertyOrField)) | ||
{ | ||
if (config.ExcludedTypes.Contains(GetMemberType(memberInfo)) || | ||
config.ExcludedMembers.Contains(memberInfo)) | ||
continue; | ||
|
||
var printingResult = GetPrintingResult(obj, memberInfo, nestingLevel); | ||
|
||
sb.Append(indentation + memberInfo.Name + " = " + printingResult); | ||
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. Вместо конкатенации лучше использовать интерполяцию |
||
} | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private string GetPrintedCollection(IEnumerable obj, int nestingLevel) | ||
{ | ||
if (obj is IDictionary dict) | ||
return GetPrintedDictionary(dict, nestingLevel); | ||
|
||
return GetPrintedSequence(obj, nestingLevel); | ||
} | ||
|
||
private string GetPrintedSequence(IEnumerable obj, int nestingLevel) | ||
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. IEnumerable заслуживает чего-то большего чем просто |
||
{ | ||
var sb = new StringBuilder(); | ||
var indentation = GetIndentation(nestingLevel); | ||
var index = 0; | ||
foreach (var element in obj) | ||
sb.Append($"{indentation}{index++}: {PrintToString(element, nestingLevel + 1)}"); | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private string GetPrintedDictionary(IDictionary dict, int nestingLevel) | ||
{ | ||
var sb = new StringBuilder(); | ||
var indentation = GetIndentation(nestingLevel); | ||
foreach (DictionaryEntry pair in dict) | ||
sb.Append($"{indentation}{pair.Key}: {PrintToString(pair.Value, nestingLevel + 1)}"); | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private string GetPrintingResult(object obj, MemberInfo memberInfo, int nestingLevel) | ||
{ | ||
string printingResult; | ||
|
||
if (config.CustomTypeSerializers.TryGetValue(GetMemberType(memberInfo), out var typeSerializer)) | ||
{ | ||
printingResult = typeSerializer.DynamicInvoke(GetMemberValue(memberInfo, obj)) + Environment.NewLine; | ||
} | ||
else if (config.CustomMemberSerializers.TryGetValue(memberInfo, out var propertySerializer)) | ||
{ | ||
printingResult = propertySerializer.DynamicInvoke(GetMemberValue(memberInfo, obj)) + Environment.NewLine; | ||
} | ||
else | ||
{ | ||
printingResult = PrintToString(GetMemberValue(memberInfo, obj), | ||
nestingLevel + 1); | ||
} | ||
|
||
return TrimResultIfNeeded(memberInfo, printingResult); | ||
} | ||
|
||
private string TrimResultIfNeeded(MemberInfo memberInfo, string printingResult) | ||
{ | ||
if (config.TrimmedMembers.TryGetValue(memberInfo, out var length)) | ||
printingResult = printingResult.Length >= length | ||
? printingResult[..length] + Environment.NewLine | ||
: printingResult; | ||
|
||
return printingResult; | ||
} | ||
|
||
private static object? GetMemberValue(MemberInfo member, Object obj) | ||
{ | ||
if (!IsPropertyOrField(member)) | ||
throw new ArgumentException("Provided member must be Field or Property"); | ||
|
||
return member.MemberType == MemberTypes.Field | ||
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 проверяет на то что member может быть только двух типов, но в реальности у нас может быть ситуация, что кто-то по невнимательности изменит метод проверки По коду вижу, что с
Ну или так:
|
||
? ((FieldInfo)member).GetValue(obj) | ||
: ((PropertyInfo)member).GetValue(obj); | ||
} | ||
|
||
private static Type GetMemberType(MemberInfo member) | ||
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 (!IsPropertyOrField(member)) | ||
throw new ArgumentException("Provided member must be Field or Property"); | ||
|
||
return member.MemberType == MemberTypes.Field | ||
? ((FieldInfo)member).FieldType | ||
: ((PropertyInfo)member).PropertyType; | ||
} | ||
|
||
private static bool IsPropertyOrField(MemberInfo member) | ||
=> member.MemberType is MemberTypes.Field or MemberTypes.Property; | ||
|
||
|
||
private static bool IsTypeFinal(Type type) | ||
=> type.IsPrimitive || type == typeof(string) || type == typeof(Guid); | ||
|
||
|
||
private static string GetIndentation(int nestingLevel) => new('\t', nestingLevel + 1); | ||
} |
This file was deleted.
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.
Не очень хорошая история объявлять интерфейсы в файле класса - наследника, даже если он один. Лучше всё-таки вынести в отдельный файл