有没有办法以编程方式提取接口?

时间:2009-01-27 17:59:23

标签: .net visual-studio refactoring

假设我有一个类似的.NET类:

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

Visual Studio有一个名为Extract Interface的漂亮的重构工具,它将提取IPerson接口,并Person实现它。有没有办法从Visual Studio外部以编程方式执行此操作?如果不能以编程方式完成,我甚至会使用shell脚本。

[编辑] 实际上,我最多有50个类,每个类都有相互依赖。

反思会起作用,但它会在整个事物中引入另一种皱纹。这些类已由xsd.exe生成。所以,如果我理解正确的话,我需要采取的步骤是:

  1. xsd.exe生成课程。
  2. 动态编译类,以便我可以使用反射。
  3. 反映它们,发出接口,并编辑原始类 实现所述接口。
  4. 一般来说,我赞成放弃接口并直接使用类,但出于各种原因我不能。

5 个答案:

答案 0 :(得分:9)

我后来遇到了同样的问题,找到了上面接受的解决方案,并决定在这里添加更多信息和细节,关于我如何使用反射和其他功能以编程方式在源代码中生成接口。 (如果您不关心使用自定义属性标记源代码,请跳过第一个代码块)

为了灵活性,我创建了名为 GenerateInterface GenerateInterfaceMember 的自定义属性(只是我自己的创作)来标记我的类和成员我想要显式导出到接口的部分(或者你无论你喜欢什么,都可以默认导出所有公共成员。例如,我将这样的属性应用于Store类......

[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...

有关custom Attributes see MSDN的信息。

代码生成资料......

注意:您可以在不使用上面显示的自定义属性的情况下执行以下所有操作。

我使用reflection来迭代类型和成员。您只想抓住类中的所有公共成员作为创建接口的候选者。

在System.Type上使用反射I获取System.Reflection.PropertyInfo,.MethodInfo等实例后,我创建了一个与语言无关的代码编译单元,由类System.CodeDom.CodeCompileUnit, of which MSDN has good example code - see it here.表示。代码编译单元实例是您需要构建的,用于描述您希望以与语言无关的方式生成的源代码。

创建CodeCompileUnit实例后,您可以将其提供给各种FCL方法以满足您的需求,例如生成源代码,在内存或磁盘中发出MSIL代码等。为了生成源代码,我使用了System.CodeDom.Compiler.CodeDomProvider See the MSDN sample in除了解决方案在下面一起被攻击...

CodeDomProvider是特定于语言的地方 - 这些语言嵌套在Microsoft命名空间下,例如:

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

有两种方法可以创建CodeDomProvider实例,比如这里的MSDN代码示例显示了一个静态方法'.CreateProvider':

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

或者,您可以直接实例化提供者

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

provider.GenerateCodeFromCompileUnit(..)之类的提供程序实例上使用方法,并将CodeCompileUnit实例提供给它。此示例代码为StringBuilder实例生成C#源代码。

// 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....

你已经生成了源代码。

在输出sourceCode字符串时,我最终会看到这样的内容。很好的微软预先设置了关于自动生成文件的评论 - 让它感觉更加“官方”的暗示。

//------------------------------------------------------------------------------
// <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);
    }
}

我的小免责声明:有很多方法可以生成和发出代码。这里显示的是以编程方式生成源代码的一种方法。有些方法有重载并且需要复杂的参数 - 你必须在MSDN中查找错综复杂的内容。有些方法在1.1,2.0等之间发生了变化。最后,一些完成代码生成的方法已经过时,已经被替代方法所取代; MSDN记录了所有这些事情。

希望此处显示的代码可以作为开始以编程方式生成接口或生成任何类型代码的指南。

注意:此示例未显示如何将生成的源代码与现有代码集成,或如何将其发布到MSIL或附加到现有的汇编文件 - 我看到的其他答案显示与这些解决方案相关的示例。

答案 1 :(得分:6)

总之:反思。

编写一些带有类对象的代码,通过其公共方法进行反映,并编写一个文本文件,这是该类实现的接口的定义,这是非常可行的。

然而,确定WHICH方法属于某个接口(例如,一个类可以实现多个,对吧?)并将正确的符号添加到类的源代码中需要做更多的工作。

答案 2 :(得分:2)

你能详细说明你为什么要这样做吗?

我无法想象我需要这种情况而不是普通的反射材料。

为什么要发出接口类型而不是直接使用类中的类型信息?

这是一个不那么快的代码示例,它从类型中发出包含所有公共属性的接口:

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");
    }

}

您可以在Reflector中加载生成的IPerson.dll以查看结果。

我认为你可以从那里建立以满足你的需求。

正确构建整个界面太复杂了,无法在此彻底讨论。

答案 3 :(得分:0)

如果我正确理解你的问题,你想编写一个程序,当给出一个类名时,将输出其方法/字段与给定类匹配的接口的源代码。您可以使用.NET的reflection库轻松编写此类程序。特别是,System.Type类具有GetMethods等方法,允许您遍历类型中定义的所有方法,并选择要包含在接口中的方法。

决定提取哪些方法是一个更难的问题。例如,您是否要包含已在其他接口中定义的方法?

答案 4 :(得分:0)

也许你可以利用xsd.exe生成的类是部分的这一事实?!