Question

How would I automate the creation of a default implementation of a class from an interface using conventions. In other words, if I have an interface:

public interface ISample
{
     int SampleID {get; set;}
     string SampleName {get; set;}
}

Is there a snippet, T4 template, or some other means of automatically generating the class below from the interface above? As you can see, I want to put the underscore before the name of the field and then make the field the same name as the property, but lower-case the first letter:

public class Sample
{
     private int _sampleID;
     public int SampleID
     {
          get { return _sampleID;}
          set { _sampleID = value; }
     }

     private string _sampleName;
     public string SampleName
     {
          get { return _sampleName;}
          set { _sampleName = value; }
     }    
}
Was it helpful?

Solution

I am not sure if T4 would be the easiest solution here in terms of readability but you can also use another code generation tool at your disposal: the CodeDom provider.

The concept is very straightforward: code consists of building blocks that you put together.

When the time is ripe, these building blocks are then parsed into the language of choice . What you end up with is a string that contains the source code of your newly created program. Afterwards you can write this to a textfile to allow for further use.

As you have noticed: there is no compile-time result, everything is runtime. If you really want compiletime then you should use T4 instead.

The code:

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text;

namespace TTTTTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            new Program();
        }

        public Program()
        {
            // Create namespace
            var myNs = new CodeNamespace("MyNamespace");
            myNs.Imports.AddRange(new[]
            {
                new CodeNamespaceImport("System"),
                new CodeNamespaceImport("System.Text")
            });

            // Create class
            var myClass = new CodeTypeDeclaration("MyClass")
            {
                TypeAttributes = TypeAttributes.Public
            };

            // Add properties to class
            var interfaceToUse = typeof (ISample);
            foreach (var prop in interfaceToUse.GetProperties())
            {
                ImplementProperties(ref myClass, prop);
            }

            // Add class to namespace
            myNs.Types.Add(myClass);

            Console.WriteLine(GenerateCode(myNs));
            Console.ReadKey();
        }

        private string GenerateCode(CodeNamespace ns)
        {
            var options = new CodeGeneratorOptions
            {
                BracingStyle = "C",
                IndentString = "    ",
                BlankLinesBetweenMembers = false
            };

            var sb = new StringBuilder();
            using (var writer = new StringWriter(sb))
            {
                CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(ns, writer, options);
            }

            return sb.ToString();
        }

        private void ImplementProperties(ref CodeTypeDeclaration myClass, PropertyInfo property)
        {
            // Add private backing field
            var backingField = new CodeMemberField(property.PropertyType, GetBackingFieldName(property.Name))
            {
                Attributes = MemberAttributes.Private
            };

            // Add new property
            var newProperty = new CodeMemberProperty
            {
                Attributes = MemberAttributes.Public | MemberAttributes.Final,
                Type = new CodeTypeReference(property.PropertyType),
                Name = property.Name
            };

            // Get reference to backing field
            var backingRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), backingField.Name);

            // Add statement to getter
            newProperty.GetStatements.Add(new CodeMethodReturnStatement(backingRef));

            // Add statement to setter
            newProperty.SetStatements.Add(
                new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), backingField.Name),
                    new CodePropertySetValueReferenceExpression()));

            // Add members to class
            myClass.Members.Add(backingField);
            myClass.Members.Add(newProperty);
        }

        private string GetBackingFieldName(string name)
        {
            return "_" + name.Substring(0, 1).ToLower() + name.Substring(1);
        }
    }

    internal interface ISample
    {
        int SampleID { get; set; }
        string SampleName { get; set; }
    }
}

This produces:

enter image description here

Magnificent, isn't it?

Sidenote: a property is given Attributes = MemberAttributes.Public | MemberAttributes.Final because omitting the MemberAttributes.Final would make it become virtual.

And last but not least: the inspiration of this awesomeness. Metaprogramming in .NET by Kevin Hazzard and Jason Bock, Manning Publications.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top