Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Implementing Adapter Pattern and Imitating Multiple Inheritance in C# using Roslyn based VS Extension Wrapper Generator

4.68/5 (22 votes)
11 Jan 2015CPOL14 min read 67.4K  
Using a single file wrapper generator to facilitate creating Adapter pattern and Multiple Inheritance

Source Code

For some reason, Codeproject site does not allow me to upload the source code. So, you can download the source code from my own web site at WrapperGenerationCode.zip.

Important Note

I would really appreciate if you leave me a comment stating what you think about this article and how it can be improved. Thanks.

Introduction

The first language I started programming was C++. Later I switched first to Java and later to C#. One and, perhaps the only, thing that I missed from C++ was multiple class inheritance. I know that multiple class inheritance is thought to be too complex and prone to errors by many people. I have a different viewpoint, however. I think that first of all, multiple class inheritance was never implemented the way it should have been in C++. Secondly, even its C++ implementation is very valuable and leads to increased code reuse and separation of concerns if applied correctly. Hopefully, reading this article through will convince you of the same.

Article Simulated Multiple Inheritance Pattern for C# talked about simulating multiple inheritance in C# by using containment instead of inheritance and creating wrappers around the methods of the contained objects. This is the approach we also adopt here, except that in our case, wrapper generation is going to be automatic - without almost any extra effort by the developer. More about this will be discussed further.

The new Roslyn functionality coming with VS 2015 creates enormous potential for analyzing the existing code and building various VS extensions. We use the Roslyn based single file generator to generate the wrapper properties, methods and events.

As was mentioned above, our multiple inheritance implementation is based on creating wrappers around objects contained within a class. Creating wrappers can also be employed for implementing other patterns, e.g., an adapter patter as will be shown below.

There are some limitations to the results and the code presented in this article. For example, we are not providing any solutions for sharing the similar super classes (or implementing virtual inheritance in C++ terms). Also, at this point, the code cannot handle generic 'super-classes' or generic methods and the error diagnostics virtually does not exist. These limitations I plan to address in the second installment of this article.

This article is organized in the following way:

  1. We go over the definition of Wrappers and show how they facilitate implementing various patterns.
  2. We provide examples of using the wrapper generation VS extension.
  3. We explain the VS extension code.

All the samples can be run using VS 2015 preview. I plan to port these samples to newer versions of VS once they are released.

I talk more about Multiple Inheritance simulation, including polymorphism and diamond inheritance in two subsequent articles: Roslyn based Simulated Multiple Inheritance Usage Patterns (Part 1) and Roslyn based Simulated Multiple Inheritance Usage Patterns (Part 2).

Wrappers and Patterns

In this section, I go over the definition and examples of Wrappers and their usage for implementing various patterns.

Wrapper Sample

A very simple example of a wrapper is located under WrapperSample project. A class Person is being wrapped within PersonWrapper class. The reason for creating Wrappers will be demonstrated a little further in the article; the purpose of this project is simply to demonstrate what the Wrappers are.

Person is a very simple class containing:

  • an event - NameChangedEvent
  • a property - Name
  • and a method - PrintInfoToConsole()
C#
public class Person
{
    // event to be wrapped
    public event Action<string> NameChangedEvent = null;

    string _name;
    // Property to be wrapped
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if (_name == value)
                return;

            _name = value;

            if (NameChangedEvent != null)
            {
                NameChangedEvent(_name);
            }
        }
    }

    public void PrintInfoToConsole()
    {
        Console.WriteLine("Name = " + Name);
    }
} 

Class PersonWrapper demonstrates how each one of these class members (an event, a property and a method) should be wrapped:

C#
public class PersonWrapper
{
    // wrapped object
    Person _person;
    public Person ThePerson
    {
        set
        {
            _person = value;
        }
    }

    // event wrapper
    public event Action<string> NameChangedEvent
    {
        add
        {
            _person.NameChangedEvent += value;
        }

        remove
        {
            _person.NameChangedEvent -= value;
        }
    }

    //property wrapper
    public string Name
    {
        get
        {
            return _person.Name;
        }
        set
        {
            _person.Name = value;
        }
    }

    // method wrapper
    public void PrintInfoToConsole()
    {
        _person.PrintInfoToConsole();
    }

    // implicit conversion operator converting
    // the wrapper object in the wrapped object
    public static implicit operator Person(PersonWrapper personWrapper)
    {
        return personWrapper._person;
    }
} 

As you can see above, on top of the wrappers for the event, the property and the method, the PersonWrapper class also has _person object, the Person setter property and an implicit conversion operator.

Person property and _person object represent the wrapped object whose members are actually called. The conversion operator converts the wrapper object into the wrapped object.

Note that the Wrapper can change a name of a wrapped event, property or method: e.g., we could have named wrapper property PersonName instead of Name. Also note that the wrapper can also change the encapsulation level, e.g., the same property Name could be made protected within the Wrapper.

Using Wrappers to Create Adapters

One of the patterns described in the famous "Gang of 4" book "Design Patterns" is Adapter pattern, also called Wrapper.

The purpose of Adapter is to adapt an already existing implementation to an already existing interface.

Assume that you have an implementation of class Person that was presented above. Assume that you have a number of methods written against an interface IHumanBeing that has all the same members as Person only differently named:

C#
public interface IHumanBeing
{
    event Action<string> HumanBeingNameChangedEvent;
    string HumanBeingName { get; set; }
    void PrintHumanBeingInfoToConsole();
}

Most of your classes and methods know how to deal with IHumanBeing, interface but have no clue about Person class. Assume also that you do not want or cannot modify the Person code, i.e., you cannot change it to implement IHumanBeing interface. This can be because a lot of your other code depends on Person class or because you do not have the Person's class code - only compiled DLL, or for any other reason.

In such case, you can adapt Person class to IHumanBeing interface. The Adapter class will implement IHumanBeing interface by wrapping the Person's events, properties and methods:

C#
public class PersonAdapter : IHumanBeing
{
    Person _person;
    public Person ThePerson
    {
        set
        {
            _person = value;
        }
    }

    public string HumanBeingsName
    {
        get
        {
            return _person.Name;
        }

        set
        {
            _person.Name = value;
        }
    }

    public event Action<string> HumanBeingsNameChangedEvent
    {
        add
        {
            _person.NameChangedEvent += value;
        }
        remove
        {
            _person.NameChangedEvent -= value;
        }
    }

    public void PrintHumanBeingsInfoToConsole()
    {
        _person.PrintInfoToConsole();
    }
}  

Project AdapterSample shows how such Adapter works.

Person, IHumanBeing and PersonAdapter have been described above.

Main class Program contains method DetectNameChange(IHumanBeing humanBeing) that accepts IHumanBeing argument, sets a handler for its HumanBeingNameChangedEvent, then sets the HumanBeingName and finally calls PrintHumanBeingsInfoToConsole() method:

C#
static void DetectNameChange(IHumanBeing humanBeing)
{
    humanBeing.HumanBeingsNameChangedEvent += HumanBeing_HumanBeingsNameChangedEvent;
    humanBeing.HumanBeingsName = "Nick";

    humanBeing.PrintHumanBeingsInfoToConsole();
}

private static void HumanBeing_HumanBeingsNameChangedEvent(string name)
{
    Console.WriteLine("New name is " + name + "\n");
}

DetectNameChange method is called on a PersonAdapter object from within Main method:

C#
static void Main(string[] args)
{
    Person person = new Person();

    PersonAdapter personAdapter = new PersonAdapter { ThePerson = person };

    DetectNameChange(personAdapter);
}  

After running the sample, you can see that all the functionality of Person class is working when accessed through the adapter - the Name is being assigned, the NameChangeEvent fires and the PrintInfoToConsole() method prints some information to console.

Speaking in general terms, the Adapter pattern changes the adapted class to satisfy a certain interface:

Image 1

As you can see, our Wrapper paradigm (with renaming) proved to be useful for implementing the Adapter pattern.

Another very similar pattern that can be implemented using the same paradigm is Proxy pattern (from the same book). Only, in this case, the Adaptee object can be nullable and a check for null is required).

Imitating Multiple Class Inheritance with the Help of Wrappers

C++ class inheritance allows the subclass to inherit the public and protected fields and methods of its super-classes. On top of that, there are implicit conversions from the subtype to each of the super-types. In general, this can be easily mimicked with the Wrappers as our example demonstrates.

The Multiple Class Inheritance sample is located within MultipleClassInheritanceSample project.

Within the sample, we 'derive' class SelectablePerson from two classes: Person and Selectable

Person is almost the same as Person of the previous examples, except that in order to simplify it, I removed from it the event.

Selectable class is also very simple - it contains IsSelected Boolean property, IsSelectedChangedEvent that fired when IsSelected changes and ToggleIsSelected() method whose name explains what it does:

C#
public class Selectable
{
    public event Action<bool> IsSelectedChangedEvent = null;

    bool _isSelected = false;
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }

        set
        {
            if (_isSelected == value)
                return;

            _isSelected = value;

            if (IsSelectedChangedEvent != null)
                IsSelectedChangedEvent(_isSelected);
        }
    }

    public void ToggleIsSelected()
    {
        IsSelected = !IsSelected;
    }
} 

SelectablePerson class contains Wrappers around members of Person and Selectable classes as well as the corresponding implicit conversion operators:

C#
public class SelectablePerson
{
    Selectable _selectable = null;
    public Selectable TheSelectable
    {
        set
        {
            _selectable = value;
        }
    }

    Person _person = null;
    public Person ThePerson
    {
        set
        {
            _person = value;
        }
    }

    public event Action<bool> IsSelectedChangedEvent
    {
        add
        {
            _selectable.IsSelectedChangedEvent += value;
        }
        remove
        {
            _selectable.IsSelectedChangedEvent -= value;
        }
    }

    public bool IsSelected
    {
        get
        {
            return _selectable.IsSelected;
        }
        set
        {
            _selectable.IsSelected = value;
        }
    }

    public void ToggleIsSelected()
    {
        _selectable.ToggleIsSelected();
    }

    // implicit conversion operator converting
    // the wrapper object in the wrapped object
    public static implicit operator Selectable(SelectablePerson selectablePerson)
    {
        return selectablePerson._selectable;
    }
    //property wrapper
    public string Name
    {
        get
        {
            return _person.Name;
        }
        set
        {
            _person.Name = value;
        }
    }

    // method wrapper
    public void PrintInfoToConsole()
    {
        _person.PrintInfoToConsole();
    }

    // implicit conversion operator converting
    // the wrapper object in the wrapped object
    public static implicit operator Person(SelectablePerson selectablePerson)
    {
        return selectablePerson._person;
    }
}

SelectablePerson class is tested in Program class:

C#
static void Main(string[] args)
{
    // create SelectablePerson
    SelectablePerson selectablePerson =
        new SelectablePerson
        {
            ThePerson = new Person(),
            TheSelectable = new Selectable()
        };

    // set name
    selectablePerson.Name = "Nick";

    // add a handler to IsSelectedChangedEvent
    selectablePerson.IsSelectedChangedEvent += SelectablePerson_IsSelectedChangedEvent;

    // toggle IsSelected
    selectablePerson.ToggleIsSelected();

    // print the person's name
    selectablePerson.PrintInfoToConsole();
}

private static void SelectablePerson_IsSelectedChangedEvent(bool isSelected)
{
    Console.WriteLine("IsSelected changed to " + isSelected);
}

We create SelectablePerson object, set its name, event handler, then toggle IsSelected property and make sure that the event handler fires. Finally, we print the Name property of the object to console.

Limitations of Wrapper based Multiple Class Inheritance

The main limitation of the Multiple Class Inheritance implementation considered above is that this reference within the super-classes does not refer to the whole object - it refers to the wrapped object. Everyone who uses this pattern needs to understand this very clearly.

Another limitation of the above implementation is that you cannot override a virtual method in the sub-class while using this method in the super-class. And this is again related to the fact that this reference within a super-class will be pointing to the wrapped super-class object and not to the Wrapper sub-class.

In spite of the above limitations, the Multiple Class Inheritance described above can be very useful especially when combined with using interface adapters.

VS Extension and its Usage

We showed how to create adapters and imitate Multiple Class Inheritance using Wrappers. However, writing a Wrapper is a tedious, error prone mechanical process. Using Roslyn, I built a VS 2015 Preview extension that generates the wrapper code on the fly based on class Attributes that specify Wrapper parameters.

In order to be able to use this VS extension, double click on NP.WrapperGenerator.vsix file within VSIX folder. The projects that use this VS extension will also have to reference the NP.WrapperAttrs.dll located under DLLs folder. NP.WrapperAttrs.dll contains the definitions of the class Attributes that specify the Wrapper parameters.

AdapterGeneratedSample and MultipleClassInheritanceGeneratedSample contain examples of generated Wrappers.

Adapter Generated Sample

Let us take a look at AdapterGeneratedSample project. All the classes of this project are identical to those of AdapterSample aside from PersonAdapter class. Here is the code for this class:

C#
[WrapsAndChanges(typeof(Person), WrappedItemsKind.Event, 
 "NameChangedEvent", "HumanBeingsNameChangedEvent")]
[WrapsAndChanges(typeof(Person), WrappedItemsKind.Property, "Name", "HumanBeingsName")]
[WrapsAndChanges(typeof(Person), WrappedItemsKind.Method, 
 "PrintInfoToConsole", "PrintHumanBeingsInfoToConsole")]
public partial class PersonAdapter : IHumanBeing
{

}  

You can see that the class is declared 'partial'. The class Attributes at the top specify the wrappers - what they wrap from which class and the name of the wrapper. In order for the code generation to take place, we need to set the "Custom Tool" property of the class to "WrapperFileCodeGenerator":

Image 2

Whenever the file is saved (e.g., by pressing Ctrl-S), the PersonAdapter.wrapper.cs file is generated. This file contain the wrappers for the class:

Image 3

Here is the content of the generated wrapper file:

C#
namespace AdapterGeneratedSample
{      
    public partial class PersonAdapter
    {  
        private Person _person;
        
        public static implicit operator Person (PersonAdapter objectToConvert)
                                   {
                                       return objectToConvert._person;
                                   }
                         
        public event Action<string> HumanBeingsNameChangedEvent
            { 
                add { _person.NameChangedEvent += value; } 
                remove { _person.NameChangedEvent -= value; } 
            }
        
        public Person ThePerson
        {
            get
            {
                return _person;
            }
            set
            {
                _person = value;
            }
        }
        
        public String HumanBeingsName
        {
            get
            {
                return _person.Name;
            }
            set
            {
                _person.Name = value;
            }
        }
        
        public void PrintHumanBeingsInfoToConsole()
        {
            _person.PrintInfoToConsole();
        }
    }
} 

Running this project produces exactly the same results as running AdapterSample project.

This sample demonstrates how to use WrapsAndChangesAttribute. Here are the explanations for the arguments of the attribute's constructor:

  1. The first argument is the type that we want to wrap.
  2. The second argument is the kind of a class member that we want to wrap (can be WrappedItemsKind.Event, WrappedItemsKind.Property or WrappedItemsKind.Method).
  3. The third argument is the name of a class member to wrap.
  4. The fourth argument is the name of the wrapper class member. If this argument is not passed, it is assumed that the wrapper and wrapped members have the same name.
  5. The fifth argument (not demonstrated in the sample above) allows to change the encapsulation level of the wrapper. It is of enum type EncapsulationLevel:
    C#
    public enum EncapsulationLevel
    {
        Unknown = 0,
        Public = 1,
        Internal = 2,
        Protected = 3,
        ProtectedInternal = 4,
        Private = 5
    }  
    Unknown means that the encapsulation level of the wrapper stays the same as that of the wrapped class member.

There is also another attribute - WrapsAttribute that can be used. It is less powerful - but more compact: less powerful because it does not allow to change the name or encapsulation method of the wrapped class member. More compact - because you can specify several objects to be wrapped within the same attribute. Its first two constructor arguments are the same as those of WrapsAndChangesAttribute. They can be followed by unlimited number of strings specifying the names of the class members to wrap, e.g.:

C#
[Wraps(typeof(Person), WrappedItemsKind.Property, "Property1", "Property2", "Property3")]  

Multiple Class Inheritance Generated Sample

Now let us take a look at MultipleClassInheritanceGeneratedSample project. The code within this project is virtually identical to that of MultipleClassInheritanceSample project with the important exception of SelectablePerson class. Here is how the new SelectablePerson code looks:

C#
[Wraps(typeof(Selectable), WrappedItemsKind.Event, "IsSelectedChangedEvent")]
[Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsSelected")]
[Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")]
[Wraps(typeof(Person), WrappedItemsKind.Property, "Name")]
[Wraps(typeof(Person), WrappedItemsKind.Method, "PrintInfoToConsole")]
public partial class SelectablePerson
{

}  

15 lines instead of 87 lines.

Just like in PersonAdapter.cs file in AdapterGeneratedSample project, the SelectablePerson.cs file should have its "Custom Tool" property set to "WrapperFileCodeGenerator". This will result in generating the file SelectablePerson.wrapper.cs that will absorb most of the complexity.

Explaining the Wrapper Generator Code

The Wrapper generator code is located under NP.WrapperGenerator solution. It consists of three projects:

  1. NP.WrapperGenerator - the main project that uses creates the NP.WrapperGenerator.vsix VS extension file.
  2. NP.WrapperAttrs - the project that defines the Wrapper attributes and some enumeration that they use. The DLL created by this project is used both by the Wrapper generator code and also by the user projects that use the wrapper generator as was shown above.
  3. NP.DOMGenerator is the project that contains most of the Wrapper generation code used by the main NP.WrapperGenerator project.

Note that we are using Roslyn for analyzing the code - getting information about the wrappers to generate, while for code generation we are using the CodeDOM instead. The reason for this is that even though Roslyn code generation is more powerful, it is terribly messy and verbose - you have to write tonnes of code to achieve very simple results and CodeDOM proved to be quite adequate for the task.

What distinguishes VS 2015 (preview) from the previous version of Visual Studio is that Roslyn now is the primary engine behind the code analysis and compilation and Roslyn workspace object is now directly accessible as one of the services from IVsSingleFileGenerator implementations.

This fact, however, (that Roslyn workspace is accessible from single file generators) was very well hidden (unintentionally of course) by Microsoft and so I give kudos to StackOverflow contributors Slaks and JoshVarty for helping me to figure out how to do it: How do you create a VS single file generator with Roslyn.

In order to start working with single file generators, I had to follow the steps specified at Roslyn to install Roslyn, VS 2015 Preview SDK and SDK Templates VSIX package.

Then I created NP.WrapperGenerator solution and project as "Visual Studio Package" - for some reason, my attempt to use "VSIX Project" template was not successful.

Then I had to add Roslyn DLLs by going to "Package Manager Console" window within your VS 2015 preview and type "Install-Package Microsoft.CodeAnalysis -Pre" (as specified at Roslyn).

I removed all the files that were created by default (aside from the source.extension.vsixmanifest file) and added WrapperFileCodeGenerator class to the file of the same name:

C#
[ComVisible(true)]
[Guid("52B316AA-1997-4c81-9969-83604C09EEB4")]
[
   CodeGeneratorRegistration
   (
       typeof(WrapperFileCodeGenerator),
       "C# Wrapper File Code Class Generator",
       "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}",
       GeneratesDesignTimeSource = true
   )
]
[ProvideObject(typeof(WrapperFileCodeGenerator))]
public class WrapperFileCodeGenerator : IVsSingleFileGenerator
{
   ...
}

After that, I added and removed references to several DLLs. Most importantly, I've added a reference to Microsoft.VisualStudio.LanguageServices.dll file without which I would not be able to get access to the Roslyn Workspace. This was, probably, the trickiest part, because this DLL is not listed in your assemblies - you have to browse to this reference - it is located under "C:\Program Files\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies\" folder. Again, kudos to Slaks for pointing this out.

After that, the reference to the Roslyn workspace can be obtained by the following code:

C#
public VisualStudioWorkspace TheWorkspace { get; set; }

public WrapperFileCodeGenerator()
{
    IComponentModel componentModel =
        (IComponentModel)Microsoft.VisualStudio.Shell.Package.GetGlobalService
        (typeof(SComponentModel));

    TheWorkspace = componentModel.GetService<visualstudioworkspace>();
}  
</visualstudioworkspace>

VS 2015 Extension functionality in combination with Roslyn will make sure that the Workspace object stays up to date while the project's code changes.

Interface IVsSingleFileGenerator implemented by our class requires two methods: DefaultExtension(out string pbstrDefaultExtension) and Generate(...).

DefaultExtensions method specifies the extension of the generated file (in our case it is ".wrapper.cs"):

C#
public int DefaultExtension(out string pbstrDefaultExtension)
{
    pbstrDefaultExtension = ".wrapper.cs";

    return VSConstants.S_OK;
}

Generate method allows to specify the byte content of the generated file:

C#
// the main method that sets the 
// wrapper's code
public int Generate
(
    string wszInputFilePath,
    string bstrInputFileContents,
    string wszDefaultNamespace,
    IntPtr[] rgbOutputFileContents,
    out uint pcbOutput,
    IVsGeneratorProgress pGenerateProgress
)
{
    byte[] codeBytes = 
        GenerateCodeBytes(wszInputFilePath, bstrInputFileContents, wszDefaultNamespace);

    int outputLength = codeBytes.Length;
    rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(outputLength);
    Marshal.Copy(codeBytes, 0, rgbOutputFileContents[0], outputLength);
    pcbOutput = (uint)outputLength;

    return VSConstants.S_OK;
}  

As you can see, all the work forming the generated file's content is done within GenerateCodeBytes(...) method called by Generate(...) method:

C#
protected byte[] GenerateCodeBytes
(string filePath, string inputFileContent, string namespaceName)
{
    // set generatedCode to empty string
    string generatedCode = "";

    // get the id of the .cs file for which we are 
    // trying to generate wrappers based on the class'es Wrapper Attributes
    DocumentId docId =
        TheWorkspace
            .CurrentSolution
            .GetDocumentIdsWithFilePath(filePath).FirstOrDefault();

    if (docId == null)
        goto returnLabel;

    // get the project that contains the file for which 
    // we are generating the wrappers.
    Project project = TheWorkspace.CurrentSolution.GetProject(docId.ProjectId);
    if (project == null)
        goto returnLabel;

    // get the compilation of the project. 
    Compilation compilation = project.GetCompilationAsync().Result;

    if (compilation == null)
        goto returnLabel;

    // get the document corresponding 
    // to the wrapper class
    Document doc = project.GetDocument(docId);

    if (doc == null)
        goto returnLabel;

    // get the Roslyn syntax tree of the document
    SyntaxTree docSyntaxTree = doc.GetSyntaxTreeAsync().Result;
    if (docSyntaxTree == null)
        goto returnLabel;

    // get the Roslyn semantic model for the document
    SemanticModel semanticModel = compilation.GetSemanticModel(docSyntaxTree);
    if (semanticModel == null)
        goto returnLabel;

    // get the document's class node
    // Note that we assume that the top class within the 
    // file is the one that we want to generate the wrappers for
    // It is better to make it the only class within the file. 
    ClassDeclarationSyntax classNode =
        docSyntaxTree.GetRoot()
            .DescendantNodes()
            .Where((node) => (node.CSharpKind() == 
             SyntaxKind.ClassDeclaration)).FirstOrDefault() as ClassDeclarationSyntax;

    if (classNode == null)
        goto returnLabel;

    // get the class type.
    INamedTypeSymbol classType = 
                     semanticModel.GetDeclaredSymbol(classNode) as INamedTypeSymbol;
    if (classType == null)
        goto returnLabel;

    // create and set the code build.
    DOMClassBuilder codeBuilder = new DOMClassBuilder();

    codeBuilder.TheCompilation = compilation;
    codeBuilder.TheTypeToExtend = classType;

    // based on the compilation and the classType 
    // objects, the codeBuild will figure out the 
    // wrapper attributes and based on them it 
    // will generate the wrapper code. 
    codeBuilder.BuildAll();

    generatedCode = codeBuilder.GetCode();

 returnLabel:
    byte[] bytes = Encoding.UTF8.GetBytes(generatedCode);

    return bytes;
}

Note that the Roslyn's Compilation object pulled out of the Project object contains all the information that you can get using System.Reflection functionality about the project without actually requiring to have a reference to the project's DLL.

Out of the Compilation objects we pull INamedTypeSymbol classType corresponding to the class for which we want to generate the Wrapper part. Both compilation and the type are assigned to a DOMClassBuild object which figures out the Wrapper attributes of the class and generates the code:

C#
codeBuilder.TheCompilation = compilation;
codeBuilder.TheTypeToExtend = classType;

// based on the compilation and the classType 
// objects, the codeBuild will figure out the 
// wrapper attributes and based on them it 
// will generate the wrapper code. 
codeBuilder.BuildAll();

generatedCode = codeBuilder.GetCode(); 

DOMClassBuilding is located under NP.DOMGenerator project. This class figures out the Wrapper attributes and based on them creates WrapsAttrView objects - they contain exactly the same information as the Wrapper attributes without the need to recreate the attribute objects themselves. Based on WrapsAttrView objects, the DOMClassBuilder calls extension methods of the static DOMCodeGenerator class to generate the actual code.

Conclusion

This article shows how to use and create the single file code generator using Roslyn VS 2015 Extension API in order to generate the wrappers of class members. This can be used for creating Adapters or simulating Multiple Class Inheritance.

The code presented here has several shortcomings:

  1. It does not treat classes with generic types well.
  2. It has no diagnostics.
  3. It does not show how to deal with virtual inheritance (inheritance when two super-classes derive from the same super-super-class).

I plan to address these shortcomings in the second part of this article.

History

  • 7th December, 2014: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)