上一篇文章中说了命名空间,你猜猜接下来该说啥。是了,命名空间下面就是类型,知道了如何生成命名空间的定义代码,之后就该学会如何声明类型了。
    CLR的类型通常有这么几种:类、接口、结构、枚举、委托。是这么几个,应该没有漏掉的吧。
    定义类型,除了委托外都可以用 CodeTypeDeclaration 类完成。CodeNamespace类公开一个Types集合,定义的类型必须添加到这个集合中,才能与命名空间关联。
    举个例子,下面代码将定义一个叫 Mouse 的类。

    1. // 编译单元
    2. CodeCompileUnit unit = new CodeCompileUnit();
    3. // 命名空间
    4. CodeNamespace nspace = new CodeNamespace();
    5. nspace.Name = "Sample";
    6. unit.Namespaces.Add(nspace);
    7. // 类声明
    8. CodeTypeDeclaration clsdcl = new CodeTypeDeclaration();
    9. clsdcl.Name = "Mouse";
    10. // 公共且密封,不让继承
    11. clsdcl.TypeAttributes = System.Reflection.TypeAttributes.Sealed | System.Reflection.TypeAttributes.Public;
    12. // 加入类型集合中
    13. nspace.Types.Add(clsdcl);
    14. CodeDomProvider prd = CodeDomProvider.CreateProvider("cs");
    15. prd.GenerateCodeFromCompileUnit(unit, Console.Out, null);

    不需要显式把 IsClass 说罢为 true,因为默认就是生成类(class)的。这里有一点老周要说明一下。
    描述类型的可访问性有两个属性可以用,一个是从 CodeTypeMember 类继承的 Attributes,由于MemberAttributes枚举不能进行组合运用(我想把类定义为 public sealed),于是我就选用了TypeAttributes,它用的值是反射里面的TypeAttributes枚举,这个枚举可以多个值组合运用。
    生成的代码如下图所示。
    image.png
    下面代码将定义一个名为 Point 的结构。

    1. CodeTypeDeclaration strdcl = new CodeTypeDeclaration("Point");
    2. strdcl.IsStruct = true;
    3. strdcl.TypeAttributes = System.Reflection.TypeAttributes.NotPublic;
    4. nspace.Types.Add(strdcl);

    NotPublic表示类型可访问性为internal,即仅限当前程序集可见。生成代码如下图所示。
    image.png

    知道怎么定义类和结构后,那么枚举就难不倒你了。

    1. CodeTypeDeclaration endcl = new CodeTypeDeclaration("AccessType");
    2. endcl.IsEnum = true;
    3. nspace.Types.Add(endcl);

    生成代码如下。
    image.png

    但是,大伙伴们一定会问,那委托呢。CodeTypeDeclaration类并不能用来声明委托类型,但它派生出了一个CodeTypeDelegate类,它是定义委托类型专业户。
    我们知道,委托类似于方法,那么你想想,委托类型需要几个要素。首先,参数列表要吧。然后,还得有个返回值。好,还是实际效果有用,不多说,来,先定义一个委托类型试试手。

    1. CodeCompileUnit unit = new CodeCompileUnit();
    2. CodeNamespace ns = new CodeNamespace("Calculators");
    3. unit.Namespaces.Add(ns);
    4. CodeTypeDelegate dl = new CodeTypeDelegate("OnAdd");
    5. // 返值为int类型
    6. dl.ReturnType = new CodeTypeReference(typeof(int));
    7. // 两个参数
    8. CodeParameterDeclarationExpression p1 = new CodeParameterDeclarationExpression();
    9. p1.Name = "x";
    10. p1.Type = new CodeTypeReference(typeof(int));
    11. CodeParameterDeclarationExpression p2 = new CodeParameterDeclarationExpression();
    12. p2.Name = "y";
    13. p2.Type = new CodeTypeReference(typeof(int));
    14. dl.Parameters.Add(p1);
    15. dl.Parameters.Add(p2);
    16. ns.Types.Add(dl);

    CodeTypeReference用于生成类型引用代码,可以用Type对象提供类型信息,也可以用字符串来提供。方法参数可以用CodeParameterDeclarationExpression类来声明,核心元素是参数类型与参数名。
    如果委托没有参数,就不用向Parameters集合添加任何东西,有几个参数就加几个。
    最后生成的委托类型代码如下。
    image.png
    下面代码生成一个无参数并返回void的委托类型。

    1. CodeTypeDelegate dl2 = new CodeTypeDelegate("DoWork");
    2. dl2.ReturnType = new CodeTypeReference(typeof(void));
    3. ns.Types.Add(dl2);

    生成的委托类型为
    image.png

    ============================================
    此时大家可能会想到,类型之间存在继承关系,比如类与类之间,类/结构可以实现接口。
    下面代码声明了两个类——A、B,其中B从A派生。

    1. CodeCompileUnit unit = new CodeCompileUnit();
    2. CodeNamespace ns = new CodeNamespace("Samples");
    3. unit.Namespaces.Add(ns);
    4. CodeTypeDeclaration t1 = new CodeTypeDeclaration("A");
    5. CodeTypeDeclaration t2 = new CodeTypeDeclaration("B");
    6. // B 从 A 派生
    7. t2.BaseTypes.Add(new CodeTypeReference(t1.Name));
    8. ns.Types.AddRange(new CodeTypeDeclaration[] { t1, t2 });

    CodeTypeDeclaration 类有一个 BaseTypes 集合,用来设置该类型的基类。
    这里你必须一个问题:虽然 BaseTypes 是一个集合,可以添加N个类型引用,可是你得遵守.NET的面向对象规则,即类不能多继承,但类型可以实现多个接口。
    尽管你可以这么搞:

    1. t2.BaseTypes.Add(new CodeTypeReference(t1.Name));
    2. t2.BaseTypes.Add(new CodeTypeReference(typeof(string)));

    然后还生成了这样的代码
    image.png
    然而,你就要想一想了,这样的代码能否通过编译,不兴趣的朋友不妨试试。
    想试试吗,好,老周就献丑了,列位看官莫笑。

    1. CodeDomProvider prd = CodeDomProvider.CreateProvider("c#");
    2. // 生成代码
    3. prd.GenerateCodeFromCompileUnit(unit, Console.Out, null);
    4. CompilerParameters options = new CompilerParameters();
    5. // 输出文件
    6. options.OutputAssembly = "test.dll";
    7. // 引用的程序集
    8. options.ReferencedAssemblies.Add("System.dll");
    9. // 开始编译
    10. CompilerResults res = prd.CompileAssemblyFromDom(options, unit);
    11. // 200% 出错
    12. if (res.Errors.Count == 0)
    13. {
    14. Console.WriteLine("编译完成。");
    15. Console.WriteLine($"输出程序集:{res.CompiledAssembly.Location}");
    16. }
    17. else
    18. {
    19. foreach (CompilerError er in res.Errors)
    20. {
    21. Console.WriteLine($"{er.ErrorNumber} - {er.ErrorText}");
    22. }
    23. }

    然后,一编译,就有好戏看了。
    image.png
    这告诉你,类A的基类不能有多个类。

    下面再看看如何实现多个接口。

    1. CodeCompileUnit unit = new CodeCompileUnit();
    2. CodeNamespace ns = new CodeNamespace("Commons");
    3. unit.Namespaces.Add(ns);
    4. // 这两个都是接口
    5. CodeTypeDeclaration t1 = new CodeTypeDeclaration("IBall");
    6. t1.IsInterface = true;
    7. CodeTypeDeclaration t2 = new CodeTypeDeclaration("IPlayer");
    8. t2.IsInterface = true;
    9. ns.Types.Add(t1);
    10. ns.Types.Add(t2);
    11. // 这个是类,实现上面两个接口
    12. CodeTypeDeclaration t3 = new CodeTypeDeclaration("FootballPlayer");
    13. t3.BaseTypes.Add(new CodeTypeReference(t1.Name));
    14. t3.BaseTypes.Add(new CodeTypeReference(t2.Name));
    15. ns.Types.Add(t3);

    t1和t2都是接口类型,t3是类,它将实现前面两个接口。运行后会生成以下代码。
    image.png