📜 ⬆️ ⬇️

Semi-automatic lazy code conversion

Good day to all. Today I want to talk about the automatic generation of C # code. For example, properties in classes that describe entities of the domain are usually described using exactly the same scheme. And it is just lazy for me to write the same constructions for each primitive property. The use of snippets and active templates saves a little, but when the need comes to change something in this scheme, you have to shovel a lot of code. So why not automatically generate this monotony during the build process?
At some point, the kinetic energy of creativity briefly overpowered the potential energy of laziness, and the result was a small library for automatically generating some program source files based on external data. I invite under the cut all lazy (in the good sense of the word) developers in C #.


Prologue


When I was programming in C ++, I used the #define directive with parameters for this purpose. A great mechanism that allows you to declare a piece of code as a simple line with inserts, which you can then reuse many times. This is not at all the same as offering templates or generic classes.
Unfortunately, the C # developers did not include support for the #define directive, similar to C ++. For this, for sure, there are reasons behind, in the first place, the readability and transparency of the code. But I still wanted to be able to avoid the need for code duplication when declaring uniform constructions.
I must say that Microsoft is quite actively using code generation. Take at least the Linq2Sql classes — all the class structure declarations are in the xml file, on the basis of which the code is generated. Moreover, the .NET Framework includes the entire System.CodeDom namespace dedicated to code generation. Numerous methods and classes allow you to generate code in terms of the CLR and save it as any language supported by .NET - C #, VB.Net. It looks like this:

/// <summary> ///   - /// </summary> /// <param name="newC"></param> /// <param name="e"></param> private void CreateProperty(CodeTypeDeclaration newC, XmlElement e) { string propName = e.GetAttribute("name"); var propType = ResolveType(e.GetAttribute("datatype")); var rel_field_prop_name = createPropertyConstName(newC, e, propName); var new_prop = new CodeMemberProperty { Attributes = MemberAttributes.Public | MemberAttributes.Final, Name = "p_" + propName, HasGet = true, HasSet = true, Type = propType }; var comment = e.GetAttribute("displayname"); if (!string.IsNullOrEmpty(comment)) new_prop.Comments.Add(new CodeCommentStatement("<summary>\n " + comment + "\n </summary>", true)); new_prop.GetStatements.Add(new CodeMethodReturnStatement( new CodeCastExpression(propType, new CodeMethodInvokeExpression( new CodeMethodReferenceExpression(new CodeBaseReferenceExpression(), "GetDataProperty"), new CodeFieldReferenceExpression(null, rel_field_prop_name))))); new_prop.SetStatements.Add( new CodeMethodInvokeExpression( new CodeMethodReferenceExpression(new CodeBaseReferenceExpression(), "SetDataProperty"), new CodeFieldReferenceExpression(null, rel_field_prop_name), new CodePropertySetValueReferenceExpression())); newC.Members.Add(new_prop); } 

')
Several times I used this mechanism for solving particular problems, and came to the conclusion that it is not suitable for my task. After all, I need to be able to quickly slip a piece of code for substitution, and the methods mentioned generate a code based on its structure. Those. To use the built-in tools, you must first write a template translator that will parse the C # code in order to generate it on its basis. Complete nonsense.


Also, it should be noted that Visual Studio includes such a possibility as the ability to create your own languages ​​and visual editing tools for them. About this you can read more here . Very interesting, but terribly cumbersome.

In 2010, Microsoft came up with T4 Text Templates . Judging by the documentation, it is almost what you need. But, first, I still have projects for the 2008 studio, and second, I could not get them to work :-(.

Still there is such a thing as Nemerle . There is generally an opportunity to invent your own language over C #. Cool, but again not what you need.
After all, I just want to be able to reuse pieces of C # code.

I want something simple


So, I have reached the state of readiness - itching to program something already. Formulated basic Wishlist:
  1. "Code generating" code should be simple and easy to read.
  2. Classes for code generation must implement Linq-style chain interfaces.
  3. The template that is used for generation should be declared as a simple string.
  4. It should be possible to combine the generated and hand-written code in one class.

And based on them, technical solutions are developed:
  1. A library is made containing classes and methods for simple code generation.
  2. The Solution includes the project of the executable application, which is assembled one of the first, immediately starts (in PostBuildStep) and generates the necessary parts for other projects using the mentioned library.
  3. To be able to combine the generated and written code we use partial classes. (Probably, this feature was added to C #, so it was not so insulting because of the lack of # define ine).
  4. The initial data for code generation is arranged, for example, in the form of enumerations. Why? Yes, it's just convenient.

The result - the code for generating a class with a set of similar properties looks like this:

 //   const string CommandPropertyTemplate = @"public static <type> <name> {{ get {{ return PVCommand.<name>.GetCommand(); }} }} "; //      var commandsClass = CodeWriter .BeginSource("Commands.cs") .Using("MyApp.Display") .BeginNamespace("MyAppCommands") .AddClass("Commands").Static(); //         //   var allcmds = System.Enum.GetValues(typeof(PVCommand)).Cast<PVCommand>(); foreach (var cmd in allcmds) { commandsClass.AddBlock<RoutedUICommand>(cmd.ToString(), CommandPropertyTemplate); } 

A few comments about the design of the template. Double curly braces are used to simply give this string to string.Format. The keywords and are replaced with the name and type of the property, which are passed as parameters to the AddBlock () method.

So why is all this necessary?


Well, now let's think a little about how all this can be applied.

Work with localized strings

In one of the projects, I had the task of localizing a WPF application, which was solved in this way . However, the addition of each new line required the insertion of a large uniform piece into XAML. When I started the next project, I decided to improve the solution using this library. So, the input is the enumeration that contains the keys of the string resources, and the values ​​for the neutral locale are placed in the Description attribute:
  public enum Strings { [Description("MyCoolApp - trial version")] AppTitle, } 


Based on this enumeration, two artifacts are generated:
  1. XAML with resources. (For its generation, classes from Linq2XML are used)
  2. Static class for convenient access to resources from code.

Generating all the code needed to work with localized strings in WPF looks like this:
 //    const string ResourceEntry = @" public const <type> <name>Key = ""<name>""; public static <type> <name> {{ get{{ return App.RString(<name>Key); }} }} "; //     var stringTable = CodeWriter .BeginSource("Strings.cs") .BeginNamespace("MyApp") .AddClass("StringTable") .Static(); //      foreach (var rstring in GetResourceStrings()) { var resourseKey = rstring.ToString(); stringTable.AddBlock<string>(resourseKey, ResourceEntry); } //   XAML void StringTableXamlTo(string dir) { var nsDefault = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; var nsX = "http://schemas.microsoft.com/winfx/2006/xaml"; var nsCore = "clr-namespace:MyApp.Core;assembly=MyApp.Core"; var resourcedict = new XElement(XName.Get("ResourceDictionary", nsDefault), new XAttribute(XName.Get("Uid", nsX), "StringTable"), new XAttribute(XNamespace.Xmlns + "core", nsCore), new XAttribute(XNamespace.Xmlns + "x", nsX)); foreach (var rstring in GetResourceStrings()) { var resourseKey = rstring.ToString(); var resourceValue = EnumHelper.GetDescription(rstring); var resourceDecl = new XElement(XName.Get("StringObject", nsCore), new XAttribute(XName.Get("Uid", nsX), "UID_" + resourseKey), new XAttribute(XName.Get("Key", nsX), resourseKey), new XAttribute("Localization.Attributes", "Value (Readable Modifiable Text)"), new XAttribute("Value", resourceValue) ); resourcedict.Add(resourceDecl); } var xaml = new XDocument(resourcedict); xaml.Save(Path.Combine(dir, "stringtable.xaml")); } } 

Now, to add a new line to the resources, you only need to add an element to the enumeration (of course, you need to remember to give it an attribute with the original value). Translation, in accordance with the technology, can be added later.

Domain Model

Another example. Here I have classes that simulate the essence of the subject area. Each class implements the INotifyPropertyChanged interface. Each of them has properties. These properties are all arranged according to the same scheme — a hidden field in which the property value is stored, getter simply returns the property value, and setter changes the field value and generates a notification of the change. I used to write each such property with my hands. Then I learned how to insert code by pattern. And now I want to describe the template in one place and list the properties with their types. The list of class properties is declared, again, as an enumeration. For each element of the enumeration, add an attribute describing the type of the property. The generation of class code is almost the same as the examples above; you just need to read the property type from the corresponding attribute. The only difference is in the property template:
 const string ModelPropertyTemplate = @" <type> _<name>; public <type> <name> {{ get {{ return _<name>; }} set {{ _<name> = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(""<name>"")); }} } "; 


Conclusion


The resulting solution allowed me to save a lot of time, and even more nerves. After all, when it was necessary to add additional actions to the property template, it was not difficult. In the previous project, when the code was static, I could not decide on its global and monotonous refactoring. And it really got on my nerves.
The absolute disadvantages of the solution include the need to allocate code generation into a separate project. Unfortunately, I haven’t managed to combine everything in one project yet, I will be grateful for the ideas.

Thanks for attention!

PS Here you can get acquainted with the source code of the mentioned library.

Source: https://habr.com/ru/post/141933/


All Articles