From 03ac9f1fbd0ee62fb5c3dc83d50a962d6d85114a Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Tue, 28 Apr 2020 16:39:56 +0300 Subject: [PATCH 1/4] Implement public property setter generation for collections Add CollectionSettersMode generator option to allow public setters generation for collection properties. Optionally new mode allows to skip backing collection fields initialization. --- XmlSchemaClassGenerator.Console/Program.cs | 28 +++++- XmlSchemaClassGenerator.Tests/Compiler.cs | 1 + XmlSchemaClassGenerator.Tests/XmlTests.cs | 97 ++++++++++++++++++- .../CollectionSettersMode.cs | 25 +++++ XmlSchemaClassGenerator/Generator.cs | 6 ++ .../GeneratorConfiguration.cs | 4 + XmlSchemaClassGenerator/TypeModel.cs | 4 +- 7 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 XmlSchemaClassGenerator/CollectionSettersMode.cs diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index bdb18e61..386e7801 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -44,6 +44,7 @@ static void Main(string[] args) var generateComplexTypesForCollections = true; var useShouldSerialize = false; var separateClasses = false; + var collectionSettersMode = CollectionSettersMode.Private; var options = new OptionSet { { "h|help", "show this message and exit", v => showHelp = v != null }, @@ -84,6 +85,30 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l { "u|enableUpaCheck", "should XmlSchemaSet check for Unique Particle Attribution (UPA) (default is enabled)", v => enableUpaCheck = v != null }, { "ct|collectionType=", "collection type to use (default is " + typeof(Collection<>).FullName + ")", v => collectionType = v == null ? typeof(Collection<>) : Type.GetType(v, true) }, { "cit|collectionImplementationType=", "the default collection type implementation to use (default is null)", v => collectionImplementationType = v == null ? null : Type.GetType(v, true) }, + { "csm|collectionSettersMode=", @"generate a private, public or public setters +without backing field initialization for collections +(default is Private; can be: {Private, Public, PublicWithoutConstructorInitialization})", + v => + { + switch (v) + { + case "pr": + case "Private": + collectionSettersMode = CollectionSettersMode.Private; + break; + case "pu": + case "Public": + collectionSettersMode = CollectionSettersMode.Public; + break; + case "puwci": + case "PublicWithoutConstructorInitialization": + collectionSettersMode = CollectionSettersMode.PublicWithoutConstructorInitialization; + break; + default: + collectionSettersMode = CollectionSettersMode.Private; + break; + } + }}, { "ctro|codeTypeReferenceOptions=", "the default CodeTypeReferenceOptions Flags to use (default is unset; can be: {GlobalReference, GenericTypeParameter})", v => codeTypeReferenceOptions = v == null ? default : (CodeTypeReferenceOptions)Enum.Parse(typeof(CodeTypeReferenceOptions), v, false) }, { "tvpn|textValuePropertyName=", "the name of the property that holds the text value of an element (default is Value)", v => textValuePropertyName = v }, { "dst|debuggerStepThrough", "generate DebuggerStepThroughAttribute (default is enabled)", v => generateDebuggerStepThroughAttribute = v != null }, @@ -159,7 +184,8 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l EnableUpaCheck = enableUpaCheck, GenerateComplexTypesForCollections = generateComplexTypesForCollections, UseShouldSerializePattern = useShouldSerialize, - SeparateClasses = separateClasses + SeparateClasses = separateClasses, + CollectionSettersMode = collectionSettersMode }; if (pclCompatible) diff --git a/XmlSchemaClassGenerator.Tests/Compiler.cs b/XmlSchemaClassGenerator.Tests/Compiler.cs index 47a4639f..6c486073 100644 --- a/XmlSchemaClassGenerator.Tests/Compiler.cs +++ b/XmlSchemaClassGenerator.Tests/Compiler.cs @@ -101,6 +101,7 @@ public static Assembly GenerateFiles(string name, IEnumerable files, Gen MemberVisitor = generatorPrototype.MemberVisitor, GenerateDescriptionAttribute = generatorPrototype.GenerateDescriptionAttribute, CodeTypeReferenceOptions = generatorPrototype.CodeTypeReferenceOptions, + CollectionSettersMode = generatorPrototype.CollectionSettersMode, TextValuePropertyName = generatorPrototype.TextValuePropertyName, EmitOrder = generatorPrototype.EmitOrder, SeparateClasses = generatorPrototype.SeparateClasses diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 6733308c..210c95f3 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -1,7 +1,7 @@ using System; using System.CodeDom; using System.Collections.Generic; -using System.Diagnostics; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Reflection; @@ -120,6 +120,101 @@ public void TestList() TestSamples("List", ListPattern); } + [Fact] + public void TestListWithPrivatePropertySetters() + { + var assembly = Compiler.Generate("List", ListPattern, new Generator() { + GenerateNullables = true, + IntegerDataType = typeof(int), + DataAnnotationMode = DataAnnotationMode.All, + GenerateDesignerCategoryAttribute = false, + GenerateComplexTypesForCollections = true, + EntityFramework = false, + GenerateInterfaces = true, + NamespacePrefix = "List", + GenerateDescriptionAttribute = true, + TextValuePropertyName = "Value", + CollectionSettersMode = CollectionSettersMode.Private + }); + Assert.NotNull(assembly); + var myClassType = assembly.GetType("List.MyClass"); + Assert.NotNull(myClassType); + var iListType = typeof(Collection<>); + var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p=>p.Name).ToList(); + var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPrivate).OrderBy(p=>p.Name).ToList(); + Assert.True(collectionPropertyInfos.Count > 0); + Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos); + + var myClassInstance = Activator.CreateInstance(myClassType); + foreach (var collectionPropertyInfo in publicCollectionPropertyInfos) + { + Assert.NotNull(collectionPropertyInfo.GetValue(myClassInstance)); + } + } + + [Fact] + public void TestListWithPublicPropertySetters() + { + var assembly = Compiler.Generate("List", ListPattern, new Generator { + GenerateNullables = true, + IntegerDataType = typeof(int), + DataAnnotationMode = DataAnnotationMode.All, + GenerateDesignerCategoryAttribute = false, + GenerateComplexTypesForCollections = true, + EntityFramework = false, + GenerateInterfaces = true, + NamespacePrefix = "List", + GenerateDescriptionAttribute = true, + TextValuePropertyName = "Value", + CollectionSettersMode = CollectionSettersMode.Public + }); + Assert.NotNull(assembly); + var myClassType = assembly.GetType("List.MyClass"); + Assert.NotNull(myClassType); + var iListType = typeof(Collection<>); + var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p=>p.Name).ToList(); + var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).ToList(); + Assert.True(collectionPropertyInfos.Count > 0); + Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos); + + var myClassInstance = Activator.CreateInstance(myClassType); + foreach (var collectionPropertyInfo in publicCollectionPropertyInfos) + { + Assert.NotNull(collectionPropertyInfo.GetValue(myClassInstance)); + } + } + + [Fact] + public void TestListWithPublicPropertySettersWithoutConstructors() + { + var assembly = Compiler.Generate("List", ListPattern, new Generator { + GenerateNullables = true, + IntegerDataType = typeof(int), + DataAnnotationMode = DataAnnotationMode.All, + GenerateDesignerCategoryAttribute = false, + GenerateComplexTypesForCollections = true, + EntityFramework = false, + GenerateInterfaces = true, + NamespacePrefix = "List", + GenerateDescriptionAttribute = true, + TextValuePropertyName = "Value", + CollectionSettersMode = CollectionSettersMode.PublicWithoutConstructorInitialization + }); + Assert.NotNull(assembly); + var myClassType = assembly.GetType("List.MyClass"); + Assert.NotNull(myClassType); + var iListType = typeof(Collection<>); + var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p=>p.Name).ToList(); + var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).ToList(); + Assert.True(collectionPropertyInfos.Count > 0); + Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos); + var myClassInstance = Activator.CreateInstance(myClassType); + foreach (var collectionPropertyInfo in publicCollectionPropertyInfos) + { + Assert.Null(collectionPropertyInfo.GetValue(myClassInstance)); + } + } + [Fact, TestPriority(1)] [UseCulture("en-US")] public void TestSimple() diff --git a/XmlSchemaClassGenerator/CollectionSettersMode.cs b/XmlSchemaClassGenerator/CollectionSettersMode.cs new file mode 100644 index 00000000..c4f81dd4 --- /dev/null +++ b/XmlSchemaClassGenerator/CollectionSettersMode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace XmlSchemaClassGenerator +{ + /// + /// Determines the kind collection accessor modifiers to emit and controls baking collection fields initialization + /// + public enum CollectionSettersMode + { + /// + /// All collection setters are private + /// + Private, + /// + /// All collection setters are public + /// + Public, + /// + /// All collection setters are public and baking collections fields not initialized in constructors + /// + PublicWithoutConstructorInitialization + } +} diff --git a/XmlSchemaClassGenerator/Generator.cs b/XmlSchemaClassGenerator/Generator.cs index 1ece78d4..628f5ff7 100644 --- a/XmlSchemaClassGenerator/Generator.cs +++ b/XmlSchemaClassGenerator/Generator.cs @@ -180,6 +180,12 @@ public CodeTypeReferenceOptions CodeTypeReferenceOptions set { _configuration.CodeTypeReferenceOptions = value; } } + public CollectionSettersMode CollectionSettersMode + { + get { return _configuration.CollectionSettersMode; } + set { _configuration.CollectionSettersMode = value; } + } + public string TextValuePropertyName { get { return _configuration.TextValuePropertyName; } diff --git a/XmlSchemaClassGenerator/GeneratorConfiguration.cs b/XmlSchemaClassGenerator/GeneratorConfiguration.cs index 2e81715e..89ea94cf 100644 --- a/XmlSchemaClassGenerator/GeneratorConfiguration.cs +++ b/XmlSchemaClassGenerator/GeneratorConfiguration.cs @@ -154,6 +154,10 @@ public NamingScheme NamingScheme /// Generator Code reference options /// public CodeTypeReferenceOptions CodeTypeReferenceOptions { get; set; } + /// + /// Determines the kind of collection accessor modifiers to emit and controls baking collection fields initialization + /// + public CollectionSettersMode CollectionSettersMode { get; set; } /// /// The name of the property that will contain the text value of an XML element diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index d66a8470..730c273a 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -830,7 +830,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi else member = new CodeMemberField(typeReference, propertyName); - var isPrivateSetter = IsCollection || isArray || (IsList && IsAttribute); + var isPrivateSetter = (IsCollection || isArray || (IsList && IsAttribute)) && Configuration.CollectionSettersMode == CollectionSettersMode.Private; if (requiresBackingField) { @@ -1011,7 +1011,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi member.CustomAttributes.AddRange(attributes); // initialize List<> - if (IsCollection || isArray || (IsList && IsAttribute)) + if ((IsCollection || isArray || (IsList && IsAttribute)) && Configuration.CollectionSettersMode != CollectionSettersMode.PublicWithoutConstructorInitialization) { var constructor = typeDeclaration.Members.OfType().FirstOrDefault(); if (constructor == null) From e6b648f54a8ec7730352245260fde2fafdf591bb Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Tue, 28 Apr 2020 16:46:00 +0300 Subject: [PATCH 2/4] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0f195924..533f09a5 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,11 @@ Options: --cit, --collectionImplementationType=VALUE the default collection type implementation to use ( default is null) + --csm, --collectionSettersMode=Private, Public, PublicWithoutConstructorInitialization + generate a private, public or public setters + without backing field initialization for collections + (default is Private; can be: Private, Public, + PublicWithoutConstructorInitialization) --ctro, --codeTypeReferenceOptions=GlobalReference, GenericTypeParameter the default CodeTypeReferenceOptions Flags to use ( default is unset; can be: GlobalReference, From d9caa9f7b40adddd5292357776e39ae7c1356453 Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Tue, 28 Apr 2020 18:56:50 +0300 Subject: [PATCH 3/4] Fix element order in collection assertions --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 210c95f3..758dc75a 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -173,7 +173,7 @@ public void TestListWithPublicPropertySetters() Assert.NotNull(myClassType); var iListType = typeof(Collection<>); var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p=>p.Name).ToList(); - var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).ToList(); + var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).OrderBy(p=>p.Name).ToList(); Assert.True(collectionPropertyInfos.Count > 0); Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos); @@ -205,7 +205,7 @@ public void TestListWithPublicPropertySettersWithoutConstructors() Assert.NotNull(myClassType); var iListType = typeof(Collection<>); var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p=>p.Name).ToList(); - var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).ToList(); + var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).OrderBy(p=>p.Name).ToList(); Assert.True(collectionPropertyInfos.Count > 0); Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos); var myClassInstance = Activator.CreateInstance(myClassType); From 99c6229fb0402ea1a07c21cce34840764a0eb96d Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Tue, 28 Apr 2020 19:10:07 +0300 Subject: [PATCH 4/4] Fix text execution order --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 758dc75a..bdd3b5bd 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -155,7 +155,7 @@ public void TestListWithPrivatePropertySetters() [Fact] public void TestListWithPublicPropertySetters() { - var assembly = Compiler.Generate("List", ListPattern, new Generator { + var assembly = Compiler.Generate("ListPublic", ListPattern, new Generator { GenerateNullables = true, IntegerDataType = typeof(int), DataAnnotationMode = DataAnnotationMode.All, @@ -187,7 +187,7 @@ public void TestListWithPublicPropertySetters() [Fact] public void TestListWithPublicPropertySettersWithoutConstructors() { - var assembly = Compiler.Generate("List", ListPattern, new Generator { + var assembly = Compiler.Generate("ListPublicWithoutConstructorInitialization", ListPattern, new Generator { GenerateNullables = true, IntegerDataType = typeof(int), DataAnnotationMode = DataAnnotationMode.All,