views:

507

answers:

5

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.

+5  A: 

In a word: reflection.

It's quite feasible to write a bit of code that takes a class object, reflects over its public methods, and writes a text file that's the definition of an interface that the class implements.

However, it's a bit more work to decide WHICH methods belong on an interface (e.g. a class may implement more than one, right?) and to add the correct notation to the class's source code.

joel.neely
A: 

If I understand your question correctly, you want to write a program that when given a class name, will output the source code for an interface whose methods/fields match the given class. You can easily write such a program using .NET's reflection libraries. In particular, the System.Type class has methods such as GetMethods which will allow you to loop over all methods defined in the type and choose which ones to include in your interface.

It is a more difficult problem to decide which methods to extract. For example, do you want to include methods already defined in other interfaces?

Brett Daniel
+1  A: 

Can you elaborate a little more on why you'd want to do this?

I could not imagine a situation where I'd need to this instead of normal Reflection stuffs.

Why would you want to emit an Interface type instead of just using the type information from the class directly?

Here's a not-so-quick code example that emits an interface with all public properties from a type:

using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

static class Program
{

    public class Person
    {
        public string Name { get; set; }
        public string Email { get; set; }
    }

    static void Main()
    {
        var personType = typeof(Person);

        // create a new assembly and module
        var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName("IPerson"),
            AssemblyBuilderAccess.RunAndSave);

        var moduleBuilder = asmBuilder.DefineDynamicModule(
            "IPerson", "IPerson.dll");

        // create a new interface type
        var typeBuilder = moduleBuilder.DefineType("IPerson",
            TypeAttributes.Public
            | TypeAttributes.Abstract
            | TypeAttributes.Interface);

        // build properties
        foreach (var prop in personType.GetProperties()) {
            var propBuilder = typeBuilder.DefineProperty(
                prop.Name, prop.Attributes,
                prop.PropertyType, Type.EmptyTypes);

            // build the getters and setters method if a public one was available
            var getter = prop.GetGetMethod();
            var setter = prop.GetSetMethod();

            var attr = MethodAttributes.Public
                | MethodAttributes.Virtual
                | MethodAttributes.Abstract;

            if (getter != null && getter.IsPublic)
                propBuilder.SetGetMethod(typeBuilder.DefineMethod(
                    getter.Name, attr,
                    getter.ReturnType,
                    getter.GetParameters().Select(p => p.ParameterType).ToArray()
                ));

            if (setter != null && setter.IsPublic)
                propBuilder.SetSetMethod(typeBuilder.DefineMethod(
                    setter.Name, attr,
                    setter.ReturnType,
                    setter.GetParameters().Select(p => p.ParameterType).ToArray()
                ));

        }

        // complete the type creation
        typeBuilder.CreateType();

        // save the result to a file
        asmBuilder.Save("IPerson.dll");
    }

}

You can load up the resulting IPerson.dll in Reflector to see the result.

I think you can build from there to meet your needs.

Building the whole interface up correctly is too complicated to discuss here thoroughly.

chakrit
A: 

Maybe you can take advantage of the fact that the classes generated by xsd.exe are partial ?!

Catalin DICU
+2  A: 

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.

John K
Oooh, me likely. Unfortunately I've moved on to a new client and don't need this any more, but thank you for a sweet answer nonetheless.
swilliams