Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to programmatically extract an Interface?

Say I have a .NET class like so:

public class Person {
    public string Name { get; set; }
    public int Id { get; set; }
}

Visual Studio has a nifty refactoring tool called Extract Interface that will extract an IPerson interface, and have Person implement it. Is there a way to do that programmatically from outside of Visual Studio? I'd even take a shell script if it can't be done programmatically.

[EDIT] In actuality, I would have up to 50 classes, each with dependencies on each other.

Reflection would work, but it would introduce another wrinkle into the whole thing. The classes are already generated by xsd.exe. So, if I understand it correctly, the steps I would need to take would be:

  1. Generate classes from xsd.exe.
  2. Compile the classes on the fly so that I can use reflection.
  3. Reflect over them, emit the interfaces, and edit the original classes to implement said interfaces.

Generally I'd be in favor of just ditching the interfaces and using the classes directly, but for various reasons I cannot.

like image 950
swilliams Avatar asked Jan 27 '09 17:01

swilliams


1 Answers

I came along later with the same question, found the accepted solution above, and decided to add more info and detail here regarding how I used reflection and other features to generate interfaces in source code programatically. (Skip this first block of code if you don't care for marking your source with custom attributes for code generation)

For flexibility I created custom Attributes named GenerateInterface and GenerateInterfaceMember (simply my own creations) to mark my classes and members for parts I want explicitly exported to interfaces (or you can just export all the public members by default, whatever you like). For example, I apply my attributes like this to a Store class...

[GenerateInterface(InterfaceName="IStore", NamespaceName="Com.Example.GenerationTest")]
class Store : BusinessObject {
    string _name;

    [GenerateInterfaceMember]
    public event EventHandler handler;

    internal Store(IDomain domain, string name)
        : base(domain) {
        _name = name;
    }

    [GenerateInterfaceMember]
    public string GetSomething(int a, int b, Random r, Store s) {
        throw new NotImplementedException();
    }
//etc...

For info on custom Attributes see MSDN.

The Code Generation Stuff...

Note: You can do all the following without using custom attributes shown above.

I used reflection to iterate over types and members. You'll want to just grab all the public members on your classes as the candidates for creating interfaces from.

After acquiring System.Reflection.PropertyInfo, .MethodInfo, etc. instances on the System.Type's using reflection I then created a Code Compile Unit which is language agnostic and is represented by the class System.CodeDom.CodeCompileUnit, of which MSDN has good example code - see it here.. The code compile unit instance is what you need to build out to describe the source code you want to generate in a language-independent fashion.

Once you have created a CodeCompileUnit instance you can feed it to various FCL methods to meet your needs, like generating source code, emitting MSIL code in memory or to disk etc. To generate source code I used System.CodeDom.Compiler.CodeDomProvider See the MSDN sample in addition to the solution hacked together below...

The CodeDomProvider is where things get language-specific - those languages are nested under the Microsoft namespace, for example:

using Microsoft.CSharp; // for C#
using Microsoft.VisualBasic; // for VB.NET

There are a couple of ways to create a CodeDomProvider instance, like the MSDN code sample here shows a static method '.CreateProvider':

CodeDomProvider provider = null;
// snip...
case "CSharp":
   provider = CodeDomProvider.CreateProvider("CSharp");
   break;
case "Visual Basic":
   provider = CodeDomProvider.CreateProvider("VisualBasic");
   break;

Alternatively you can directly instantiate a provider

Microsoft.CSharp.CSharpCodeProvider csProvider = new CSharpCodeProvider();
Microsoft.VisualBasic.VBCodeProvider vbProvider = new VBCodeProvider();

Use methods on the provider instance like provider.GenerateCodeFromCompileUnit(..) and feed your CodeCompileUnit instance to it. This sample code generates C# source code to a StringBuilder instance.

// me setting up string capture for my purposes
StringBuilder szBuilder = new StringBuilder();
StringWriter writer = new StringWriter(szBuilder);

// the key statement - writes c# code to StringBuilder using provider instance and compile unit instance
csProvider.GenerateCodeFromCompileUnit(compileUnit, writer, new CodeGeneratorOptions());

// solidifying the generated C# code into one string
string sourceCode = szBuilder.ToString(); 
// can write this string to a file, etc....

And there you have generated source code.

I end up with something that looks like this when outputting the sourceCode string. It's nice Microsoft prepends the comments about an auto-generated file - makes it feel more "offical" methinks.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.4200
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Com.Example.GenerationTest {
    using System;
    using Com.Example.BusinessCore;


    public interface IStore {

        string Name {
            get;
            set;
        }

         event System.EventHandler handler;

        string GetSomething(int a, int b, System.Random r, Store s);
    }
}

My little disclaimer: There are many ways to generate and emit code. Shown here is one way to generate source code programmatically. Some methods have overloads and take complex arguments - you'll have to look up intricacies in MSDN. Some methods have changed between 1.1, 2.0 etc. Lastly, some ways of accomplishing code generation are obsolete and have been supplanted by alternate means; MSDN notes all these things.

Hopefully the code shown here acts as a guide to start moving ahead with generating interfaces programmatically, or generating any kind of code for that matter.

Note: This example doesn't show how to integrate the generated source code with existing code, or how to emit it into MSIL or append to existing assembly files - other answers I see posted show samples relating to those kinds of solutions.

like image 180
8 revs Avatar answered Nov 15 '22 12:11

8 revs