diff --git a/ObjectPrinting/IPropertyPrintingConfig.cs b/ObjectPrinting/IPropertyPrintingConfig.cs new file mode 100644 index 000000000..d58756ec7 --- /dev/null +++ b/ObjectPrinting/IPropertyPrintingConfig.cs @@ -0,0 +1,8 @@ +using System; + +namespace ObjectPrinting; + +public interface IPropertyPrintingConfig +{ + PrintingConfig Using(Func serializer); +} \ No newline at end of file diff --git a/ObjectPrinting/ITypePrintingConfig.cs b/ObjectPrinting/ITypePrintingConfig.cs new file mode 100644 index 000000000..d67f91632 --- /dev/null +++ b/ObjectPrinting/ITypePrintingConfig.cs @@ -0,0 +1,8 @@ +using System; + +namespace ObjectPrinting; + +public interface ITypePrintingConfig +{ + PrintingConfig Using(Func serializer); +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..b2ce90a79 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,9 @@ -namespace ObjectPrinting +namespace ObjectPrinting; + +public class ObjectPrinter { - public class ObjectPrinter + public static PrintingConfig For() { - public static PrintingConfig For() - { - return new PrintingConfig(); - } + return new PrintingConfig(); } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs new file mode 100644 index 000000000..88c4d583f --- /dev/null +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -0,0 +1,11 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrinterExtensions +{ + public static string PrintToString(this T obj) => ObjectPrinter.For().PrintToString(obj); + + public static string PrintToString(this T obj, Func, PrintingConfig> config) => + config(ObjectPrinter.For()).PrintToString(obj); +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..a68679825 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,398 @@ using System; -using System.Linq; +using System.Collections; +using System.Collections.Generic; using System.Text; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig { - public class PrintingConfig + private readonly IReadOnlySet _typesToExclude; + private readonly IReadOnlySet _membersToExclude; + private readonly IReadOnlyDictionary _cultureSerializers; + private readonly IReadOnlyDictionary _typeSerializers; + private readonly IReadOnlyDictionary _memberSerializers; + private readonly int _maxNestingLevel; + + public PrintingConfig() + { + _typesToExclude = new HashSet(); + _membersToExclude = new HashSet(); + _cultureSerializers = new Dictionary(); + _typeSerializers = new Dictionary(); + _memberSerializers = new Dictionary(); + _maxNestingLevel = 5; + } + + private PrintingConfig( + IReadOnlySet typesToExclude, + IReadOnlySet membersToExclude, + IReadOnlyDictionary cultureSerializers, + IReadOnlyDictionary typeSerializers, + IReadOnlyDictionary memberSerializers, + int maxNestingLevel) + { + _typesToExclude = typesToExclude; + _membersToExclude = membersToExclude; + _cultureSerializers = cultureSerializers; + _typeSerializers = typeSerializers; + _memberSerializers = memberSerializers; + _maxNestingLevel = maxNestingLevel; + } + + public PrintingConfig SetMaxNestingLevel(int maxNestingLevel) + { + if (maxNestingLevel < 0) + throw new ArgumentException("Max nesting level must be greater than or equal to 0."); + + return new PrintingConfig( + _typesToExclude, + _membersToExclude, + _cultureSerializers, + _typeSerializers, + _memberSerializers, + maxNestingLevel); + } + + public PrintingConfig Exclude() + { + var newTypesToExclude = new HashSet(_typesToExclude) { typeof(TPropType) }; + return new PrintingConfig( + newTypesToExclude, + _membersToExclude, + _cultureSerializers, + _typeSerializers, + _memberSerializers, + _maxNestingLevel); + } + + public PrintingConfig Exclude(Expression> propertySelector) + { + var memberInfo = GetMemberInfo(propertySelector); + ValidateMemberInfo(memberInfo); + + var newMembersToExclude = new HashSet(_membersToExclude) { memberInfo }; + return new PrintingConfig( + _typesToExclude, + newMembersToExclude, + _cultureSerializers, + _typeSerializers, + _memberSerializers, + _maxNestingLevel); + } + + public IPropertyPrintingConfig Printing(Expression> propertySelector) + { + var memberInfo = GetMemberInfo(propertySelector); + ValidateMemberInfo(memberInfo); + + return new PropertyPrintingConfig(this, memberInfo); + } + + public ITypePrintingConfig Printing() + { + return new TypePrintingConfig(this); + } + + public ITypePrintingConfig PrintSettings() + { + return Printing(); + } + + public IPropertyPrintingConfig PrintPropertySettings( + Expression> propertySelector) + { + return Printing(propertySelector); + } + + public PrintingConfig UseCulture(CultureInfo culture) where TPropType : IFormattable + { + return SetCulture(culture); + } + + public PrintingConfig SetCulture(CultureInfo culture) where TPropType : IFormattable + { + var newCultureSerializers = new Dictionary(_cultureSerializers) + { + [typeof(TPropType)] = culture + }; + + return new PrintingConfig( + _typesToExclude, + _membersToExclude, + newCultureSerializers, + _typeSerializers, + _memberSerializers, + _maxNestingLevel); + } + + internal PrintingConfig WithTypeSerializer(Func serializeFunc) + { + var newTypeSerializers = new Dictionary(_typeSerializers) + { + [typeof(TPropType)] = serializeFunc + }; + + return new PrintingConfig( + _typesToExclude, + _membersToExclude, + _cultureSerializers, + newTypeSerializers, + _memberSerializers, + _maxNestingLevel); + } + + internal PrintingConfig WithMemberSerializer(MemberInfo memberInfo, Func serializeFunc) + { + var newMemberSerializers = new Dictionary(_memberSerializers) + { + [memberInfo] = serializeFunc + }; + + return new PrintingConfig( + _typesToExclude, + _membersToExclude, + _cultureSerializers, + _typeSerializers, + newMemberSerializers, + _maxNestingLevel); + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 1, new HashSet(ReferenceEqualityComparer.Instance)); + } + + private string PrintToString(object? obj, int nestingLevel, ISet processedObjects) + { + if (obj == null) + return "null"; + + var type = obj.GetType(); + + if ((type.IsPrimitive || type == typeof(string) || type == typeof(Guid) || type == typeof(DateTime)) + && _typesToExclude.Contains(type)) + return $""; + + if (processedObjects.Contains(obj)) + return $"Cyclic reference detected: {type.Name}"; + + if (_typeSerializers.TryGetValue(type, out var typeSerializer)) + { + var result = typeSerializer.DynamicInvoke(obj) as string; + return result ?? "null"; + } + + if (type.IsPrimitive || type == typeof(string) || type == typeof(Guid) || type == typeof(DateTime)) + return type == typeof(string) ? obj.ToString() : FormatWithCulture(obj, type); + + if (nestingLevel > _maxNestingLevel) + return $"... (max nesting level {_maxNestingLevel} reached)"; + + processedObjects.Add(obj); + + try + { + if (obj is IEnumerable enumerable && type != typeof(string)) + return SerializeEnumerable(enumerable, nestingLevel, processedObjects); + + return SerializeObject(obj, nestingLevel, processedObjects, type); + } + finally + { + processedObjects.Remove(obj); + } + } + + private string FormatWithCulture(object obj, Type type) + { + if (_cultureSerializers.TryGetValue(type, out var culture) && obj is IFormattable formattable) + return formattable.ToString(null, culture); + + return obj.ToString() ?? "null"; + } + + private string SerializeObject(object obj, int nestingLevel, ISet processedObjects, Type type) + { + var indentation = new string('\t', nestingLevel); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + + var members = type.GetProperties().Cast() + .Concat(type.GetFields().Cast()); + + foreach (var member in members) + { + if (ShouldExcludeMember(member)) + continue; + + var value = GetMemberValue(member, obj); + var serializedValue = SerializeMember(member.Name, value, member.GetMemberType(), + nestingLevel, processedObjects, indentation, member); + + sb.AppendLine(serializedValue); + } + + return sb.ToString(); + } + + private bool ShouldExcludeMember(MemberInfo member) + { + var memberType = member.GetMemberType(); + var shouldExclude = _typesToExclude.Contains(memberType) || + _membersToExclude.Contains(member); + + return shouldExclude; + } + + private string SerializeMember(string memberName, object? value, Type memberType, + int nestingLevel, ISet processedObjects, string indentation, MemberInfo memberInfo) { - public string PrintToString(TOwner obj) + if (_memberSerializers.TryGetValue(memberInfo, out var memberSerializer)) { - return PrintToString(obj, 0); + var result = memberSerializer.DynamicInvoke(value); + return $"{indentation}{memberName} = {result}"; } - private string PrintToString(object obj, int nestingLevel) + if (value != null && _typeSerializers.TryGetValue(memberType, out var typeSerializer)) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + var result = typeSerializer.DynamicInvoke(value); + return $"{indentation}{memberName} = {result}"; + } + + if (value is IFormattable formattable && _cultureSerializers.TryGetValue(memberType, out var culture)) + return $"{indentation}{memberName} = {formattable.ToString(null, culture)}"; + + return $"{indentation}{memberName} = {PrintToString(value, nestingLevel + 1, processedObjects)}"; + } - var finalTypes = new[] + private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel, ISet processedObjects) +{ + if (enumerable is IDictionary dictionary) + return SerializeDictionary(dictionary, nestingLevel, processedObjects); + + return SerializeList(enumerable, nestingLevel, processedObjects); +} + + private string SerializeList(IEnumerable enumerable, int nestingLevel, ISet processedObjects) + { + var items = new List(); + int totalCount = 0; + const int maxItems = 100; + bool hasMoreItems = false; + + foreach (var item in enumerable) + { + if (totalCount >= maxItems) { - 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()) + hasMoreItems = true; + break; + } + + items.Add(item); + totalCount++; + } + + if (items.Count == 0) + return "[]"; + + var indentation = new string('\t', nestingLevel); + var sb = new StringBuilder(); + sb.AppendLine("["); + + for (int i = 0; i < items.Count; i++) + { + var serializedItem = PrintToString(items[i], nestingLevel + 1, processedObjects); + sb.Append($"{indentation}{serializedItem}"); + + if (i < items.Count - 1) + sb.AppendLine(","); + else + sb.AppendLine(); + } + + if (hasMoreItems) + sb.AppendLine($"{indentation}... (showing first {maxItems} items)"); + + sb.Append($"{new string('\t', nestingLevel - 1)}]"); + return sb.ToString(); + } + + private string SerializeDictionary(IDictionary dictionary, int nestingLevel, ISet processedObjects) + { + var indentation = new string('\t', nestingLevel); + var sb = new StringBuilder(); + sb.AppendLine("["); + + int count = 0; + const int maxItems = 100; + bool hasMoreItems = false; + + foreach (DictionaryEntry entry in dictionary) + { + if (count >= maxItems) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + hasMoreItems = true; + break; } - return sb.ToString(); + + var serializedKey = PrintToString(entry.Key, nestingLevel + 1, processedObjects); + var serializedValue = PrintToString(entry.Value, nestingLevel + 1, processedObjects); + + sb.AppendLine($"{indentation}{serializedKey}: {serializedValue}"); + count++; } + + if (hasMoreItems) + sb.AppendLine($"{indentation}... (showing first {maxItems} items)"); + + sb.Append($"{new string('\t', nestingLevel - 1)}]"); + return count == 0 ? "[]" : sb.ToString(); + } + + private static MemberInfo GetMemberInfo(Expression> expression) + { + if (expression.Body is MemberExpression memberExpr) + return memberExpr.Member; + + if (expression.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression operandMemberExpr) + return operandMemberExpr.Member; + + throw new ArgumentException("Expression must be a property or field access"); + } + + private static void ValidateMemberInfo(MemberInfo memberInfo) + { + if (memberInfo.MemberType != MemberTypes.Property && memberInfo.MemberType != MemberTypes.Field) + throw new ArgumentException("Expression must reference a property or field"); + } + + private static object? GetMemberValue(MemberInfo member, object obj) + { + if (member is PropertyInfo property) + return property.GetValue(obj); + + if (member is FieldInfo field) + return field.GetValue(obj); + + return null; + } +} + +public static class MemberInfoExtensions +{ + public static Type GetMemberType(this MemberInfo member) + { + if (member is PropertyInfo property) + return property.PropertyType; + + if (member is FieldInfo field) + return field.FieldType; + + throw new ArgumentException("Member must be a property or field"); } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 000000000..6b8a22e3c --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,21 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig : IPropertyPrintingConfig +{ + private readonly PrintingConfig _parentConfig; + private readonly MemberInfo _memberInfo; + + public PropertyPrintingConfig(PrintingConfig parentConfig, MemberInfo memberInfo) + { + _parentConfig = parentConfig; + _memberInfo = memberInfo; + } + + public PrintingConfig Using(Func serializer) + { + return _parentConfig.WithMemberSerializer(_memberInfo, serializer); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 000000000..4a9bb21df --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,33 @@ +using System; + +namespace ObjectPrinting; + +public static class PropertyPrintingConfigExtensions +{ + public static PrintingConfig TrimmedTo( + this IPropertyPrintingConfig config, + int length) + { + return ImplementTrimmedTo(config, length); + } + + private static PrintingConfig ImplementTrimmedTo( + IPropertyPrintingConfig config, + int length) + { + if (length < 0) + throw new ArgumentException("Length must be non-negative", nameof(length)); + + return config.Using(str => + { + if (str == null) + return "null"; + + var stringValue = str.ToString(); + if (stringValue == null || stringValue.Length <= length) + return stringValue ?? "null"; + + return stringValue[..length]; + }); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553c..000000000 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public static class ObjectExtensions - { - public static string PrintToString(this T obj) - { - return ObjectPrinter.For().PrintToString(obj); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectPrinter.cs b/ObjectPrinting/Solved/ObjectPrinter.cs deleted file mode 100644 index 540ee769c..000000000 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index 0ec5aeb2b..000000000 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Text; - -namespace ObjectPrinting.Solved -{ - public class PrintingConfig - { - public PropertyPrintingConfig Printing() - { - return new PropertyPrintingConfig(this); - } - - public PropertyPrintingConfig Printing(Expression> memberSelector) - { - return new PropertyPrintingConfig(this); - } - - public PrintingConfig Excluding(Expression> memberSelector) - { - return this; - } - - internal PrintingConfig Excluding() - { - return this; - } - - 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(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs deleted file mode 100644 index a509697d1..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; - -namespace ObjectPrinting.Solved -{ - public class PropertyPrintingConfig : IPropertyPrintingConfig - { - private readonly PrintingConfig printingConfig; - - public PropertyPrintingConfig(PrintingConfig printingConfig) - { - this.printingConfig = printingConfig; - } - - public PrintingConfig Using(Func print) - { - return printingConfig; - } - - public PrintingConfig Using(CultureInfo culture) - { - return printingConfig; - } - - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; - } - - public interface IPropertyPrintingConfig - { - PrintingConfig ParentConfig { get; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs deleted file mode 100644 index dd3922394..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved -{ - public static class PropertyPrintingConfigExtensions - { - public static string PrintToString(this T obj, Func, PrintingConfig> config) - { - return config(ObjectPrinter.For()).PrintToString(obj); - } - - public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) - { - return ((IPropertyPrintingConfig)propConfig).ParentConfig; - } - - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index ac52d5ee5..000000000 --- a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; - -namespace ObjectPrinting.Solved.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For() - //1. Исключить из сериализации свойства определенного типа - .Excluding() - //2. Указать альтернативный способ сериализации для определенного типа - .Printing().Using(i => i.ToString("X")) - //3. Для числовых типов указать культуру - .Printing().Using(CultureInfo.InvariantCulture) - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .Printing(p => p.Name).TrimmedToLength(10) - //6. Исключить из сериализации конкретного свойства - .Excluding(p => p.Age); - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - - //8. ...с конфигурированием - string s3 = person.PrintToString(s => s.Excluding(p => p.Age)); - Console.WriteLine(s1); - Console.WriteLine(s2); - Console.WriteLine(s3); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/Person.cs b/ObjectPrinting/Solved/Tests/Person.cs deleted file mode 100644 index 858ebbf8d..000000000 --- a/ObjectPrinting/Solved/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index 4c8b2445c..000000000 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NUnit.Framework; - -namespace ObjectPrinting.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs deleted file mode 100644 index f95559554..000000000 --- a/ObjectPrinting/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 000000000..72745f3c6 --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,18 @@ +using System; + +namespace ObjectPrinting; + +public class TypePrintingConfig : ITypePrintingConfig +{ + private readonly PrintingConfig _parentConfig; + + public TypePrintingConfig(PrintingConfig parentConfig) + { + _parentConfig = parentConfig; + } + + public PrintingConfig Using(Func serializer) + { + return _parentConfig.WithTypeSerializer(serializer); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/CollectionSerializeTests.cs b/ObjectPrintingTests/CollectionSerializeTests.cs new file mode 100644 index 000000000..af34e1fc4 --- /dev/null +++ b/ObjectPrintingTests/CollectionSerializeTests.cs @@ -0,0 +1,133 @@ +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.Test; + +namespace ObjectPrintingTests; + +[TestFixture] +public class CollectionSerializeTests +{ + [Test] + public void PrintToString_ShouldSerializeListCorrectly() + { + var list = new List { 1, 2, 3, 4, 5 }; + + var result = ObjectPrinter.For>().PrintToString(list); + + var expected = "[\r\n\t1,\r\n\t2,\r\n\t3,\r\n\t4,\r\n\t5\r\n]"; + result.Should().Be(expected); + } + + [Test] + public void PrintToString_ShouldSerializeEmptyList() + { + var list = new List(); + + var result = ObjectPrinter.For>().PrintToString(list); + + result.Should().Be("[]"); + } + + [Test] + public void PrintToString_ShouldSerializeArray() + { + var array = new[] { "a", "b", "c" }; + + var result = ObjectPrinter.For().PrintToString(array); + + var expected = "[\r\n\ta,\r\n\tb,\r\n\tc\r\n]"; + result.Should().Be(expected); + } + + [Test] + public void PrintToString_ShouldSerializeDictionary() + { + var dict = new Dictionary + { + { "one", 1 }, + { "two", 2 } + }; + + var result = ObjectPrinter.For>().PrintToString(dict); + + result.Should().Contain("one").And.Contain("1") + .And.Contain("two").And.Contain("2") + .And.Contain("["); + } + + [Test] + public void PrintToString_ShouldSerializeNestedCollections() + { + var matrix = new List> + { + new() { 1, 2 }, + new() { 3, 4 } + }; + + var result = ObjectPrinter.For>>().PrintToString(matrix); + + result.Should().Contain("1").And.Contain("2") + .And.Contain("3").And.Contain("4") + .And.Contain("["); + } + + [Test] + public void PrintToString_ShouldSerializeCollectionWithObjects() + { + var people = new List + { + new() { Name = "John", Age = 25 }, + new() { Name = "Jane", Age = 30 } + }; + + var result = ObjectPrinter.For>().PrintToString(people); + + result.Should().Contain("John").And.Contain("25") + .And.Contain("Jane").And.Contain("30") + .And.Contain("Person") + .And.Contain("["); + } + + [Test] + public void PrintToString_ShouldHandleNullInCollections() + { + var listWithNulls = new List { "a", null, "c" }; + + var result = ObjectPrinter.For>().PrintToString(listWithNulls); + + var expected = "[\r\n\ta,\r\n\tnull,\r\n\tc\r\n]"; + result.Should().Be(expected); + } + + [Test] + public void ExcludeType_ShouldWorkWithCollections() + { + var people = new List + { + new() { Name = "John", Age = 25 }, + new() { Name = "Jane", Age = 30 } + }; + + var result = ObjectPrinter.For>() + .Exclude() + .PrintToString(people); + + result.Should().NotContain("Name =") + .And.Contain("Age = 25") + .And.Contain("Age = 30"); + } + + [Test] + public void PrintSettings_ShouldApplyToCollectionElements() + { + var numbers = new List { 10, 20, 30 }; + + var result = ObjectPrinter.For>() + .PrintSettings() + .Using(i => $"#{i}#") + .PrintToString(numbers); + + var expected = "[\r\n\t#10#,\r\n\t#20#,\r\n\t#30#\r\n]"; + result.Should().Be(expected); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Family.cs b/ObjectPrintingTests/Family.cs new file mode 100644 index 000000000..c8a42600b --- /dev/null +++ b/ObjectPrintingTests/Family.cs @@ -0,0 +1,8 @@ +namespace ObjectPrinting.Test; + +public record Family +{ + public Person? Mom { get; set; } + public Person? Dad { get; set; } + public List Children { get; set; } = []; +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs new file mode 100644 index 000000000..6320cb05e --- /dev/null +++ b/ObjectPrintingTests/ObjectPrinterAcceptanceTests.cs @@ -0,0 +1,68 @@ +using System.Globalization; + +namespace ObjectPrinting.Test; + +[TestFixture] +public class ObjectPrinterAcceptanceTests +{ + [Test] + public void Demo() + { + var person = new Person + { + Name = "Alex", + Age = 19, + BestFriend = new Person + { + Name = "Bob", + Age = 40, + BestFriend = new Person() + }, + Friends = + [ + new Person + { + Name = "Alice", + Age = 19, + }, + new Person + { + Name = "Robert", + Age = 19, + }, + ], + BodyParts = + { + { "Hand", 2 }, + { "Foot", 2 }, + { "Head", 1 }, + { "Tail", 0 } + } + }; + + var printer = ObjectPrinter.For() + //1. Исключить из сериализации свойства определенного типа + .Exclude() + //2. Указать альтернативный способ сериализации для определенного типа + .PrintSettings().Using(i => i.ToString("X")) + //3. Для числовых типов указать культуру + .UseCulture(CultureInfo.InvariantCulture) + //4. Настроить сериализацию конкретного свойства + .PrintPropertySettings(x => x.Surname).Using(p => $"-{p}-") + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .PrintPropertySettings(p => p.Name).TrimmedTo(2) + //6. Исключить из сериализации конкретного свойства + .Exclude(p => p.Age); + + var s1 = printer.PrintToString(person); + Console.WriteLine(s1); + + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + var s2 = person.PrintToString(); + Console.WriteLine(s2); + + //8. ...с конфигурированием + var s3 = person.PrintToString(p => p.Exclude(x => x.Name)); + Console.WriteLine(s3); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.csproj b/ObjectPrintingTests/ObjectPrintingTests.csproj new file mode 100644 index 000000000..e5b8d2681 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.csproj @@ -0,0 +1,28 @@ + + + + net9.0 + latest + enable + enable + false + + + + + + + + + + + + + + + + + + + + diff --git a/ObjectPrintingTests/Person.cs b/ObjectPrintingTests/Person.cs new file mode 100644 index 000000000..aa0336c4d --- /dev/null +++ b/ObjectPrintingTests/Person.cs @@ -0,0 +1,13 @@ +namespace ObjectPrinting.Test; + +public record Person +{ + public Guid Id { get; set; } + public string? Name { get; set; } + public string? Surname { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public Person? BestFriend { get; set; } + public Person[] Friends { get; set; } = []; + public Dictionary BodyParts { get; set; } = new(); +} \ No newline at end of file diff --git a/ObjectPrintingTests/PrintingConfigTests.cs b/ObjectPrintingTests/PrintingConfigTests.cs new file mode 100644 index 000000000..7d211171e --- /dev/null +++ b/ObjectPrintingTests/PrintingConfigTests.cs @@ -0,0 +1,520 @@ +using System.Globalization; +using FluentAssertions; +using ObjectPrinting.Test; + +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrintingComprehensiveTests +{ + private Person firstPerson; + private Person secondPerson; + private Family family; + + [SetUp] + public void Setup() + { + firstPerson = new() + { + Name = "Ben", + Surname = "Big", + Height = 170.1, + Age = 20, + BestFriend = new() { Name = "Bob", Surname = "Boby", Height = 40, Age = 80 }, + Friends = + [ + new() { Name = "Alice", Surname = "Sev", Height = 50, Age = 30 }, + new() { Name = "Max", Surname = "Albor", Height = 10, Age = 9 } + ], + BodyParts = { { "Hand", 2 }, { "Foot", 2 }, { "Head", 1 }, { "Tail", 0 } } + }; + + secondPerson = new(); + family = new() { Mom = firstPerson, Dad = secondPerson, Children = [firstPerson, secondPerson] }; + } + + [Test] + public void PrintToString_ShouldSerializePersonWithAllProperties() + { + var result = ObjectPrinter.For().PrintToString(firstPerson); + + result.Should().Contain("Name = Ben") + .And.Contain("Surname = Big") + .And.Contain("Age = 20") + .And.Contain("Height = 170,1") + .And.Contain("BestFriend = Person"); + } + + [Test] + public void PrintToString_ShouldSerializeNestedBestFriend() + { + var result = ObjectPrinter.For().PrintToString(firstPerson); + + result.Should().Contain("BestFriend = Person") + .And.Contain("Name = Bob") + .And.Contain("Surname = Boby") + .And.Contain("Age = 80") + .And.Contain("Height = 40"); + } + + [Test] + public void PrintToString_ShouldSerializeFriendsList() + { + var result = ObjectPrinter.For().PrintToString(firstPerson); + + result.Should().Contain("Friends = [") + .And.Contain("Name = Alice") + .And.Contain("Name = Max") + .And.Contain("Surname = Sev") + .And.Contain("Surname = Albor"); + } + + [Test] + public void PrintToString_ShouldSerializeBodyPartsDictionary() + { + var result = ObjectPrinter.For().PrintToString(firstPerson); + + result.Should().Contain("BodyParts = [") + .And.Contain("Hand") + .And.Contain("Foot") + .And.Contain("Head") + .And.Contain("Tail") + .And.Contain("2") + .And.Contain("1") + .And.Contain("0"); + } + + [Test] + public void ExcludeTypeString_ShouldRemoveNameAndSurname() + { + var result = ObjectPrinter.For() + .Exclude() + .PrintToString(firstPerson); + + result.Should().NotContain("Name =") + .And.NotContain("Surname =") + .And.Contain("Age = 20") + .And.Contain("Height = 170,1"); + } + + [Test] + public void ExcludePropertyAge_ShouldRemoveOnlyAge() + { + var result = ObjectPrinter.For() + .Exclude(p => p.Age) + .PrintToString(firstPerson); + + result.Should().NotContain("Age =") + .And.Contain("Name = Ben") + .And.Contain("Surname = Big") + .And.Contain("Height = 170,1"); + } + + [Test] + public void PrintSettingsForInt_ShouldFormatAgeInHex() + { + var result = ObjectPrinter.For() + .PrintSettings() + .Using(i => i.ToString("X")) + .PrintToString(firstPerson); + + result.Should().Contain("Age = 14") + .And.Contain("BodyParts"); + } + + [Test] + public void PrintPropertySettingsForName_ShouldApplyCustomFormatting() + { + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .Using(name => $"Mr. {name}") + .PrintToString(firstPerson); + + result.Should().Contain("Name = Mr. Ben") + .And.Contain("Surname = Big"); + } + + [Test] + public void TrimmedTo_ShouldShortenName() + { + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .TrimmedTo(2) + .PrintToString(firstPerson); + + result.Should().Contain("Name = Be") + .And.Contain("Surname = Big"); + } + + [Test] + public void UseCultureForDouble_ShouldFormatHeightWithCulture() + { + var result = ObjectPrinter.For() + .UseCulture(new CultureInfo("en-US")) + .PrintToString(firstPerson); + + result.Should().Contain("Height = 170.1"); + } + + [Test] + public void PrintToString_ShouldHandleCircularReferencesInFamily() + { + var result = ObjectPrinter.For().PrintToString(family); + + result.Should().NotContain("StackOverflow") + .And.NotContain("Infinite loop"); + } + + [Test] + public void SetMaxNestingLevel_ShouldLimitComplexObjectDepth() + { + var result = ObjectPrinter.For() + .SetMaxNestingLevel(2) + .PrintToString(firstPerson); + + result.Should().Contain("max nesting level"); + } + + [Test] + public void ExcludeBestFriend_ShouldRemoveNestedObject() + { + var result = ObjectPrinter.For() + .Exclude(p => p.BestFriend) + .PrintToString(firstPerson); + + result.Should().NotContain("BestFriend = Person") + .And.Contain("Name = Ben") + .And.Contain("Friends = ["); + } + + [Test] + public void ExcludeFriends_ShouldRemoveListButKeepOtherProperties() + { + var result = ObjectPrinter.For() + .Exclude(p => p.Friends) + .PrintToString(firstPerson); + + result.Should().NotContain("Friends = [") + .And.Contain("Name = Ben") + .And.Contain("BestFriend = Person") + .And.Contain("BodyParts = ["); + } + + [Test] + public void MultipleExclusions_ShouldWorkTogether() + { + var result = ObjectPrinter.For() + .Exclude(p => p.Age) + .Exclude(p => p.Height) + .Exclude(p => p.BestFriend) + .PrintToString(firstPerson); + + result.Should().NotContain("Age =") + .And.NotContain("Height =") + .And.NotContain("BestFriend =") + .And.Contain("Name = Ben") + .And.Contain("Friends = [") + .And.Contain("BodyParts = ["); + } + + [Test] + public void TypeAndPropertyExclusionCombination_ShouldWork() + { + var result = ObjectPrinter.For() + .Exclude() + .Exclude(p => p.Age) + .PrintToString(firstPerson); + + result.Should().NotContain("Name =") + .And.NotContain("Surname =") + .And.NotContain("Age =") + .And.Contain("Height = 170,1") + .And.Contain("BestFriend = Person"); + } + + [Test] + public void PrintSettingsForString_ShouldAffectAllStringProperties() + { + var result = ObjectPrinter.For() + .PrintSettings() + .Using(str => str?.ToUpper() ?? "NULL") + .PrintToString(firstPerson); + + result.Should().Contain("Name = BEN") + .And.Contain("Surname = BIG") + .And.Contain("BestFriend = Person") + .And.Contain("Name = BOB"); + } + + [Test] + public void ComplexConfiguration_ShouldApplyAllSettings() + { + var result = ObjectPrinter.For() + .Exclude() + .PrintSettings().Using(i => (i * 10).ToString()) + .PrintPropertySettings(p => p.Name).TrimmedTo(1) + .UseCulture(new CultureInfo("en-US")) + .Exclude(p => p.Surname) + .PrintToString(firstPerson); + + result.Should().NotContain("Id") + .And.NotContain("Surname =") + .And.Contain("Age = 200") + .And.Contain("Name = B") + .And.Contain("Height = 170.1") + .And.Contain("BestFriend = Person"); + } + + [Test] + public void PrintToString_ExtensionMethod_ShouldWorkWithFirstPerson() + { + var result = firstPerson.PrintToString(); + + result.Should().Contain("Name = Ben") + .And.Contain("Age = 20") + .And.Contain("BestFriend = Person"); + } + + [Test] + public void PrintToString_WithConfigExtension_ShouldWork() + { + var result = firstPerson.PrintToString(config => + config.Exclude(p => p.Age) + .PrintPropertySettings(p => p.Name).TrimmedTo(1)); + + result.Should().NotContain("Age =") + .And.Contain("Name = B") + .And.Contain("Surname = Big"); + } + + [Test] + public void PrintToString_ShouldHandleEmptySecondPerson() + { + var result = ObjectPrinter.For().PrintToString(secondPerson); + + result.Should().Contain("Person") + .And.Contain("Name = null") + .And.Contain("Age = 0") + .And.Contain("Height = 0") + .And.Contain("BestFriend = null") + .And.Contain("Friends = []") + .And.Contain("BodyParts = []"); + } + + [Test] + public void PrintToString_ShouldSerializeFamilyStructure() + { + var result = ObjectPrinter.For().PrintToString(family); + + result.Should().Contain("Mom = Person") + .And.Contain("Dad = Person") + .And.Contain("Children = [") + .And.Contain("Name = Ben") + .And.Contain("Name = null"); + } + + [Test] + public void PropertySerialization_ShouldOverrideTypeSerializationForSpecificProperty() + { + var result = ObjectPrinter.For() + .PrintSettings().Using(str => $"TYPE_{str}") + .PrintPropertySettings(p => p.Name).Using(str => $"PROP_{str}") + .PrintToString(firstPerson); + + result.Should().Contain("Name = PROP_Ben") + .And.Contain("Surname = TYPE_Big"); + } + + [Test] + public void PrintToString_ShouldNotHaveStackOverflowWithComplexStructure() + { + var action = () => ObjectPrinter.For().PrintToString(firstPerson); + + action.Should().NotThrow(); + } + + [Test] + public void MultipleTypeSerializations_LastOneShouldWin() + { + var result = ObjectPrinter.For() + .PrintSettings().Using(str => "first") + .PrintSettings().Using(str => "second") + .PrintToString(firstPerson); + + result.Should().Contain("Name = second") + .And.Contain("Surname = second") + .And.NotContain("first"); + } + + [Test] + public void MultiplePropertySerializations_LastOneShouldWin() + { + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .Using(name => "first") + .PrintPropertySettings(p => p.Name) + .Using(name => "second") + .PrintToString(firstPerson); + + result.Should().Contain("Name = second") + .And.NotContain("first"); + } + + [Test] + public void Exclusion_ShouldHavePriorityOverSerialization() + { + var result = ObjectPrinter.For() + .PrintSettings().Using(str => "modified") + .Exclude() + .PrintToString(firstPerson); + + result.Should().NotContain("Name =") + .And.NotContain("Surname =") + .And.NotContain("modified"); + } + + [Test] + public void PrintToString_ShouldHandleDictionaryValuesCorrectly() + { + var result = ObjectPrinter.For().PrintToString(firstPerson); + + result.Should().Contain("Hand").And.Contain("2") + .And.Contain("Foot").And.Contain("2") + .And.Contain("Head").And.Contain("1") + .And.Contain("Tail").And.Contain("0"); + } + + [Test] + public void SetMaxNestingLevel_WithValidValue_ShouldNotThrow() + { + var action = () => ObjectPrinter.For() + .SetMaxNestingLevel(5) + .PrintToString(firstPerson); + + action.Should().NotThrow(); + } + + [Test] + public void TrimmedTo_WithValidLength_ShouldNotThrow() + { + var action = () => ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .TrimmedTo(3) + .PrintToString(firstPerson); + + action.Should().NotThrow(); + } + + [Test] + public void PrintToString_ShouldMaintainObjectStructureIntegrity() + { + var result = ObjectPrinter.For().PrintToString(firstPerson); + + result.Should().Contain("Person") + .And.Contain("BestFriend = Person") + .And.Contain("Friends = [") + .And.Contain("BodyParts = [") + .And.Contain("]"); + } + + [Test] + public void PropertyExclusion_ShouldBeClassSpecific() + { + var result = ObjectPrinter.For() + .Exclude(f => f.Mom.Name) + .PrintToString(family); + + var lines = result.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + + var momLineIndex = Array.FindIndex(lines, line => line.Trim() == "Mom = Person"); + momLineIndex.Should().BeGreaterThan(-1, "Mom should be present"); + + var linesAfterMom = lines.Skip(momLineIndex + 1).Take(10).ToArray(); + var hasMomName = linesAfterMom.Any(line => line.Contains("Name = Ben")); + hasMomName.Should().BeFalse("Mom.Name should be excluded"); + + result.Should().Contain("Dad = Person"); + } + + [Test] + public void PropertySerialization_ShouldBeClassSpecific() + { + var result = ObjectPrinter.For() + .PrintPropertySettings(f => f.Mom.Name) + .Using(name => $"MOM_{name}") + .PrintToString(family); + + result.Should().Contain("MOM_Ben"); + + var hasRegularName = result.Contains("Name = null") || result.Contains("Surname = Big"); + hasRegularName.Should().BeTrue("There should be regular non-modified properties"); + } + + [Test] + public void LargeCollection_ShouldBeLimited() + { + var largeList = Enumerable.Range(1, 150).ToList(); + + var result = ObjectPrinter.For>().PrintToString(largeList); + + result.Should().Contain("100") + .And.Contain("... (showing first 100 items)") + .And.NotContain("101"); + } + + [Test] + public void EmptyCollection_ShouldSerializeAsEmpty() + { + var emptyList = new List(); + + var result = ObjectPrinter.For>().PrintToString(emptyList); + + result.Should().Be("[]"); + } + + [Test] + public void CollectionWithFewItems_ShouldShowAll() + { + var smallList = new List { 1, 2, 3 }; + + var result = ObjectPrinter.For>().PrintToString(smallList); + + result.Should().Contain("1") + .And.Contain("2") + .And.Contain("3") + .And.NotContain("..."); + } + + [Test] + public void Dictionary_ShouldBeLimitedWhenLarge() + { + var largeDict = new Dictionary(); + for (int i = 0; i < 150; i++) + { + largeDict[$"key{i}"] = i; + } + + var result = ObjectPrinter.For>().PrintToString(largeDict); + + result.Should().Contain("key0") + .And.Contain("... (showing first 100 items)") + .And.NotContain("key100"); + } + + [Test] + public void NestedCollections_ShouldRespectLimits() + { + var nestedList = new List> + { + Enumerable.Range(1, 50).ToList(), + Enumerable.Range(51, 60).ToList() + }; + + var result = ObjectPrinter.For>>().PrintToString(nestedList); + + result.Should().Contain("1") + .And.Contain("50") + .And.Contain("51") + .And.Contain("110"); + } +} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9ed..c09e2207a 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrintingTests", "ObjectPrintingTests\ObjectPrintingTests.csproj", "{551D8E24-3373-48A8-ACE2-3E478D200D2A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {551D8E24-3373-48A8-ACE2-3E478D200D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {551D8E24-3373-48A8-ACE2-3E478D200D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {551D8E24-3373-48A8-ACE2-3E478D200D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {551D8E24-3373-48A8-ACE2-3E478D200D2A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ecb..53fe49b2f 100644 --- a/fluent-api.sln.DotSettings +++ b/fluent-api.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016