-
Notifications
You must be signed in to change notification settings - Fork 262
HomeworkV2 #250
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
base: master
Are you sure you want to change the base?
HomeworkV2 #250
Changes from all commits
484d8fe
7f314ec
2e0bbd6
a42948b
6bd26bd
da663d6
ca5e647
d05a834
56499f6
edaaf3e
5280988
f638fe6
437537c
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,57 @@ | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Text; | ||
| using ObjectPrinting.Interface; | ||
|
|
||
| namespace ObjectPrinting; | ||
|
|
||
| internal class EnumerablePrinter | ||
| { | ||
| public string PrintEnumerable( | ||
| IEnumerable enumerable, | ||
| Type type, | ||
|
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. Передаем только чтобы в StringBuilder записать? |
||
| int level, | ||
| IPrintingConfigInternal config, | ||
| HashSet<object> visited, | ||
| ObjectSerializer serializer) | ||
| { | ||
| var indent = new string('\t', level); | ||
| var sb = new StringBuilder(); | ||
|
|
||
| sb.AppendLine(type.Name); | ||
|
|
||
| if (enumerable is IDictionary dict) | ||
| { | ||
| foreach (DictionaryEntry entry in dict) | ||
| { | ||
| sb.Append(indent + "\t"); | ||
| sb.Append($"[{entry.Key}] = {serializer.PrintInternal(entry.Value, level + 1, config, visited)}"); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| var i = 0; | ||
| foreach (var item in enumerable) | ||
| { | ||
| sb.Append(indent + "\t"); | ||
|
|
||
| if (item != null && | ||
| item.GetType().IsGenericType && | ||
| item.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) | ||
| { | ||
| dynamic kv = item; | ||
| sb.Append($"[{kv.Key}] = {serializer.PrintInternal(kv.Value, level + 1, config, visited)}"); | ||
| } | ||
| else | ||
| { | ||
| sb.Append($"[{i}] = {serializer.PrintInternal(item, level + 1, config, visited)}"); | ||
| } | ||
|
|
||
| i++; | ||
| } | ||
| } | ||
|
|
||
| return sb.ToString(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| using System; | ||
| using System.Globalization; | ||
| using ObjectPrinting.Interface; | ||
|
|
||
| namespace ObjectPrinting; | ||
|
|
||
| public class FormattableMemberConfig<TOwner, TProp> : | ||
| IFormattableMemberConfig<TOwner, TProp> | ||
| where TProp : IFormattable | ||
| { | ||
| private readonly PrintingConfig<TOwner> config; | ||
| private readonly string memberName; | ||
|
|
||
| public FormattableMemberConfig(PrintingConfig<TOwner> config, string memberName) | ||
| { | ||
| this.config = config; | ||
| this.memberName = memberName; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Using(CultureInfo culture) | ||
| { | ||
| config.PropertySerializers[memberName] = obj => | ||
| ((IFormattable)obj).ToString(null, culture); | ||
| return config; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| using System; | ||
| using System.Globalization; | ||
|
|
||
| namespace ObjectPrinting.Interface; | ||
|
|
||
| public interface IFormattableMemberConfig<TOwner, TProp> | ||
| where TProp : IFormattable | ||
| { | ||
| PrintingConfig<TOwner> Using(CultureInfo culture); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using System; | ||
|
|
||
| namespace ObjectPrinting.Interface; | ||
|
|
||
| public interface IMemberConfig<TOwner, TProp> | ||
| { | ||
| PrintingConfig<TOwner> Using(Func<TProp, string> serializer); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace ObjectPrinting.Interface; | ||
|
|
||
| internal interface IPrintingConfigInternal | ||
| { | ||
| HashSet<Type> ExcludedTypes { get; } | ||
| HashSet<string> ExcludedProperties { get; } | ||
|
|
||
| Dictionary<Type, Func<object, string>> TypeSerializers { get; } | ||
| Dictionary<string, Func<object, string>> PropertySerializers { get; } | ||
| Dictionary<string, int> TrimLengths { get; } | ||
| public int? GlobalStringTrimLength { get; } | ||
|
Comment on lines
+8
to
+14
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. Я продолжаю мочь добавлять элементы напрямую в коллекции. Зачем мне мучиться с контекстами, валидациями и т.д, если я могу просто написать TrimLength["Name"]=0 |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| namespace ObjectPrinting.Interface; | ||
|
|
||
| public interface IStringMemberConfig<TOwner> | ||
| { | ||
| PrintingConfig<TOwner> Trim(int maxLength); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using ObjectPrinting.Interface; | ||
|
|
||
| namespace ObjectPrinting; | ||
|
|
||
| internal class MemberPrinter | ||
| { | ||
| public string PrintMember( | ||
| string memberName, | ||
| object value, | ||
| Type memberType, | ||
| IPrintingConfigInternal config, | ||
| int level, | ||
| HashSet<object> visited, | ||
| ObjectSerializer serializer) | ||
| { | ||
| if (value == null) | ||
| return memberName + " = null" + Environment.NewLine; | ||
|
|
||
| if (config.PropertySerializers.TryGetValue(memberName, out var serializerFunc)) | ||
| return memberName + " = " + serializerFunc(value) + Environment.NewLine; | ||
|
|
||
| if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) | ||
| return memberName + " = " + typeSerializer(value) + Environment.NewLine; | ||
|
|
||
| if (value is string s) | ||
| { | ||
| int? maxLen = null; | ||
|
|
||
| if (config.TrimLengths.TryGetValue(memberName, out var propTrim)) | ||
| maxLen = propTrim; | ||
| else if (config.GlobalStringTrimLength.HasValue) | ||
| maxLen = config.GlobalStringTrimLength.Value; | ||
|
|
||
| if (maxLen.HasValue && s.Length > maxLen.Value) | ||
| s = s.Substring(0, maxLen.Value); | ||
|
|
||
| return memberName + " = " + s + Environment.NewLine; | ||
| } | ||
|
|
||
| if (TypeHelper.IsSimpleType(memberType)) | ||
| return memberName + " = " + value + Environment.NewLine; | ||
|
|
||
| return memberName + " = " + serializer.PrintInternal(value, level, config, visited); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| using System; | ||
| using System.Globalization; | ||
| using ObjectPrinting.Interface; | ||
|
|
||
| namespace ObjectPrinting; | ||
|
|
||
| public class MemberPrintingConfig<TOwner, TProp> | ||
| { | ||
| private readonly PrintingConfig<TOwner> config; | ||
| private readonly string memberName; | ||
|
|
||
| internal MemberPrintingConfig(PrintingConfig<TOwner> config, string memberName) | ||
| { | ||
| this.config = config; | ||
| this.memberName = memberName; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Using(Func<TProp, string> serializer) | ||
| { | ||
| config.PropertySerializers[memberName] = obj => serializer((TProp)obj); | ||
| return config; | ||
| } | ||
|
Comment on lines
+18
to
+22
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. unused |
||
|
|
||
| public IStringMemberConfig<TOwner> AsString() | ||
| { | ||
| return typeof(TProp) == typeof(string) | ||
| ? new StringMemberConfig<TOwner>(config, memberName) | ||
| : throw new InvalidOperationException("Property is not string"); | ||
| } | ||
|
Comment on lines
+24
to
+29
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. С точки зрения интерфейса выглядит ультра странно. Я и так передал конкретное поле с конкретным типом. Но чтобы появилась пачка методов, я должен еще раз сказать тип. Допустим, у нас появятся для других типов кастомные методы. Мы также будем дописывать AsGuid, AsEnum, AsInt? 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. Плюсом, у нас возможно исключение. Что вообще весь пользовательский опыт рушит |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using System; | ||
|
|
||
| namespace ObjectPrinting; | ||
|
|
||
| public static class ObjectPrinterExtensions | ||
| { | ||
| public static string PrintToString<T>( | ||
| this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config) | ||
| { | ||
| var cfg = config(new PrintingConfig<T>()); | ||
| return cfg.PrintToString(obj); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using ObjectPrinting.Interface; | ||
|
|
||
| namespace ObjectPrinting; | ||
|
|
||
| public class ObjectSerializer | ||
| { | ||
| private readonly MemberPrinter memberPrinter = new MemberPrinter(); | ||
| private readonly EnumerablePrinter enumerablePrinter = new EnumerablePrinter(); | ||
|
|
||
| public string Print<TOwner>(TOwner obj, PrintingConfig<TOwner> config) | ||
| { | ||
| var visited = new HashSet<object>(new ReferenceEqualityComparer()); | ||
| return PrintInternal(obj, 0, config, visited); | ||
| } | ||
|
|
||
| internal string PrintInternal( | ||
| object obj, | ||
| int level, | ||
| IPrintingConfigInternal config, | ||
| HashSet<object> visited) | ||
| { | ||
| if (obj == null) | ||
| return "null" + Environment.NewLine; | ||
|
Comment on lines
+27
to
+28
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, так и укажи nullable-type через "?". типа object? obj |
||
|
|
||
| var type = obj.GetType(); | ||
|
|
||
| if (TypeHelper.IsSimpleType(type)) | ||
| return obj + Environment.NewLine; | ||
|
|
||
| if (!type.IsValueType && !visited.Add(obj)) | ||
| return $"<cyclic reference to {type.Name}>" + Environment.NewLine; | ||
|
|
||
| if (obj is IEnumerable enumerable) | ||
| return enumerablePrinter.PrintEnumerable(enumerable, type, level, config, visited, this); | ||
|
|
||
| var indent = new string('\t', level); | ||
| var sb = new StringBuilder(); | ||
|
|
||
| sb.AppendLine(type.Name); | ||
|
|
||
| foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) | ||
| { | ||
| if (property.GetIndexParameters().Length > 0) continue; | ||
| if (config.ExcludedTypes.Contains(property.PropertyType)) continue; | ||
| if (config.ExcludedProperties.Contains(property.Name)) continue; | ||
|
|
||
| var value = property.GetValue(obj); | ||
| sb.Append(indent + "\t" + | ||
| memberPrinter.PrintMember(property.Name, value, property.PropertyType, config, level + 1, visited, this)); | ||
| } | ||
|
|
||
| foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) | ||
| { | ||
| if (config.ExcludedTypes.Contains(field.FieldType)) continue; | ||
| if (config.ExcludedProperties.Contains(field.Name)) continue; | ||
|
|
||
| var value = field.GetValue(obj); | ||
| sb.Append(indent + "\t" + | ||
| memberPrinter.PrintMember(field.Name, value, field.FieldType, config, level + 1, visited, this)); | ||
| } | ||
|
Comment on lines
+46
to
+65
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. DRY так и осталось |
||
|
|
||
| return sb.ToString(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,63 @@ | ||
| using System; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Collections.Generic; | ||
| using System.Globalization; | ||
| using System.Linq.Expressions; | ||
| using ObjectPrinting.Interface; | ||
|
|
||
| namespace ObjectPrinting | ||
| namespace ObjectPrinting; | ||
|
|
||
| public class PrintingConfig<TOwner> : IPrintingConfigInternal | ||
| { | ||
| public class PrintingConfig<TOwner> | ||
| public HashSet<Type> ExcludedTypes { get; } = new(); | ||
| public HashSet<string> ExcludedProperties { get; } = new(); | ||
|
|
||
| public Dictionary<Type, Func<object, string>> TypeSerializers { get; } = new(); | ||
| public Dictionary<string, Func<object, string>> PropertySerializers { get; } = new(); | ||
| public Dictionary<string, int> TrimLengths { get; } = new(); | ||
|
Comment on lines
+11
to
+16
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. Все это отображается в интелисенсе, что будет мешать изучению сервиса через fluent подход.
Author
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 int? GlobalStringTrimLength { get; private set; } | ||
|
|
||
| private readonly ObjectSerializer objectSerializer = new ObjectSerializer(); | ||
|
|
||
| public PrintingConfig<TOwner> TrimStringsGlobal(int maxLength) | ||
| { | ||
| GlobalStringTrimLength = maxLength; | ||
| return this; | ||
| } | ||
|
Comment on lines
+21
to
+25
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. В целом так сделать можно. Но мы на этом уровне не работаем с конкретными типами. Лучше, чтобы было типа такого: config.Printing().Trimmed(5) 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 PrintingConfig Trimmed( |
||
|
|
||
| public PrintingConfig<TOwner> Excluding<TProp>() | ||
| { | ||
| ExcludedTypes.Add(typeof(TProp)); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Excluding<TProp>(Expression<Func<TOwner, TProp>> selector) | ||
| { | ||
| var name = ((MemberExpression)selector.Body).Member.Name; | ||
|
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. Под эту ситуацию нужны также тесты |
||
| ExcludedProperties.Add(name); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Printing<TProp>(Func<TProp, string> selector) | ||
| { | ||
| TypeSerializers[typeof(TProp)] = o => selector((TProp)o); | ||
| return this; | ||
| } | ||
|
|
||
| public TypePrintingConfig<TOwner, TProp> Printing<TProp>() | ||
| { | ||
| return new TypePrintingConfig<TOwner, TProp>(this); | ||
| } | ||
|
|
||
| public MemberPrintingConfig<TOwner, TProp> SelectMember<TProp>( | ||
|
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. До этого и для исключения типа, и для исключения поля мы используем один и тот же метод. А здесь для поля - SelectMember, для типа - Printing. Давай к одному виду приведем, дабы избежать непонимания 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. Ну и опять же. Если у меня будет поле AnotherPerson с свойством Name, то ты его тоже по-другому сериализовать будешь, а я его даже не выбирал. |
||
| Expression<Func<TOwner, TProp>> selector) | ||
| { | ||
| var name = ((MemberExpression)selector.Body).Member.Name; | ||
| return new MemberPrintingConfig<TOwner, TProp>(this, name); | ||
| } | ||
|
|
||
| public string PrintToString(TOwner obj) | ||
| { | ||
| 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(); | ||
| } | ||
| return objectSerializer.Print(obj, this); | ||
| } | ||
| } | ||
|
|
||
| } | ||
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.
Я наоборот против того, чтобы выносить отдельно. У тебя есть конкретный контекст у этого сервиса, который тянется из родительского класса - config, visited. serializer.
Также нам теперь приходится передавать и тип, и сам объект. Это уже нарушение инкапсуляции