前文中,老周已经厚着脸皮介绍了类型的声明,类型里面包含的自然就是类型成员了,故,顺着这个思路,今天咱们就了解一下如何向类型添加成员。

咱们都知道,常见的类型成员,比如字段、属性、方法、事件。表示代码成员的类型与 CodeTypeDeclaration 类有着共同的基类—— CodeTypeMember。毕竟类型也好,类型成员也好,都有共同特征。

下面,简单认识一下相关的几个类型,心里也有个谱。

CodeMemberField:字段

CodeMemberProperty:属性

CodeMemberMethod:方法

CodeConstructor:构造函数

CodeMemberEvent:事件

下面来个例子,声明一个Dog类,并且为它添加一个带有单个 string 类型参数的构造函数,以及一个名为Age的属性。

            /*
* 从外向里,层层创建
*/
// 编译单元
CodeCompileUnit unit = new CodeCompileUnit();
// 命名空间
CodeNamespace ns = new CodeNamespace("Sample");
// 引入一个System命名空间
ns.Imports.Add(new CodeNamespaceImport(nameof(System)));
unit.Namespaces.Add(ns); // 声明类型
CodeTypeDeclaration t1 = new CodeTypeDeclaration("Dog");
ns.Types.Add(t1);
// 准备用于构造函数的参数
CodeParameterDeclarationExpression p = new CodeParameterDeclarationExpression(typeof(string), "dogName");
// 构造函数
CodeConstructor ctr = new CodeConstructor();
ctr.Parameters.Add(p);
t1.Members.Add(ctr); // 属性
CodeMemberProperty prty = new CodeMemberProperty();
prty.Name = "Age";
// 属性值的类型
prty.Type = new CodeTypeReference(typeof(int));
// 公共属性
prty.Attributes = MemberAttributes.Public;
prty.HasGet = true; //可读
prty.HasSet = true; //可写
t1.Members.Add(prty);

在使用 CodeMemberProperty 定义属性时,HasGet和HasSet属性用于指定属性是否可读或可写。

上面例子生成的代码如下:

这里生成的属性是带有 virtual 关键字,即虚成员,将来可以被派生类 override 的。关于如何去掉这个 virtual 关键字,可以把它变成 Finnal 成员。 关于这个 Attributes 属性,老周向大伙伴们表示歉意。

因为 MemberAttributes 枚举没有附加 Flags 特性说明,老周当初以为它不可以进行组合使用,不过,后来老周经过试验,发现这个浑蛋是可以进行位运算的。老周计划后面再弄一篇文章来说这个事情。

比如,上面的 Age 属性,如果想去掉 virtual 关键字,可以这样写:

prty.Attributes = MemberAttributes.Public | MemberAttributes.Final;

再举一个例子,声明一个名为Demo的类,里面有个私有字段 ma, 通过公共的构造函数中的参数把数据传递给 ma 字段。

            CodeCompileUnit unit = new CodeCompileUnit();
CodeNamespace ns = new CodeNamespace("Data");
unit.Namespaces.Add(ns);
ns.Imports.Add(new CodeNamespaceImport(nameof(System))); // 声明类型
CodeTypeDeclaration type1 = new CodeTypeDeclaration("Demo");
ns.Types.Add(type1); // 字段
CodeMemberField fd = new CodeMemberField(typeof(string), "ma");
fd.Attributes = MemberAttributes.Private; //私有
type1.Members.Add(fd); // 构造函数
// 参数
CodeParameterDeclarationExpression pc = new CodeParameterDeclarationExpression(typeof(string), "a");
CodeConstructor ctor = new CodeConstructor();
type1.Members.Add(ctor);
ctor.Parameters.Add(pc);
// 公共
ctor.Attributes = MemberAttributes.Public;
// 赋值语句
CodeAssignStatement ass = new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fd.Name), new CodeVariableReferenceExpression(pc.Name));
// 将赋值语句加入到方法体中
ctor.Statements.Add(ass);

用 CodeMemberField 声明字段很好办,只要指定字段类型和名字就行了。 构造函数是一个特殊的方法,CodeConstructor也继承了一个Statements 集合,表示的是方法体中的语句。在上面例子中,构造函数内只要一个赋值语句就够了。

生成的C#代码如下图所示。

-------------------------------------------------------------------------------------

下面看看方法的生成,先举个简单点的例子,生成一个叫PlayMusic,无参数,返回值类型为void的方法。

            CodeMemberMethod m = new CodeMemberMethod();
m.Name = "PlayMusic";
CodeDomProvider prvd = CodeDomProvider.CreateProvider("cs");
prvd.GenerateCodeFromMember(m, Console.Out, null);

生成结果如下图所示。

再复杂一点,下面咱们生成一个类,再于类中定义一个带两个参数,且返回非void类型的方法。

            CodeTypeDeclaration tp = new CodeTypeDeclaration("Calculator");
tp.Attributes = MemberAttributes.Public; CodeMemberMethod m = new CodeMemberMethod();
tp.Members.Add(m);
// 方法名称
m.Name = "Add";
// 公共方法
m.Attributes = MemberAttributes.Public | MemberAttributes.Final;
// 返回值
m.ReturnType = new CodeTypeReference(typeof(int));
// 两个参数
CodeParameterDeclarationExpression p1 = new CodeParameterDeclarationExpression(typeof(int), "m");
CodeParameterDeclarationExpression p2 = new CodeParameterDeclarationExpression(typeof(int), "n");
m.Parameters.Add(p1);
m.Parameters.Add(p2);
/*
* 在方法中生成语句 return m + n
*/
// 运算表达式 m+n
CodeBinaryOperatorExpression addexp = new CodeBinaryOperatorExpression();
addexp.Operator = CodeBinaryOperatorType.Add;
addexp.Left = new CodeVariableReferenceExpression(p1.Name);
addexp.Right = new CodeVariableReferenceExpression(p2.Name);
// return 语句
CodeMethodReturnStatement retstm = new CodeMethodReturnStatement(addexp);
m.Statements.Add(retstm); CodeDomProvider provider = CodeDomProvider.CreateProvider("cs");
CodeGeneratorOptions opt = new CodeGeneratorOptions
{
BracingStyle = "C"
};
provider.GenerateCodeFromType(tp, Console.Out, opt);

ReturnType用于设置方法的返回值类型,方法参数使用 CodeParameterDeclarationExpression 来定义,属性于表达式。在方法的代码块中,如果要进行返回,就要使用 return 语句,CodeMethodReturnStatement类表示的就是return语句。使用时要指定一个用计算返回值的表达式,如果不提供表达式,就直接返回(跳出),好比如这样:

return;

结果会生成这样的代码:

方法成员里面,有一种方法比较特殊,它就是入口点,即Main方法,声明入口点方法,可以用专门的类—— CodeEntryPointMethod。初始化时无需指定名字,因为它的名字是固定的。入口点的返回值类型通常是 void 或者 int,参数一般是一个字符串数组,表示传入的命令行参数,当然也可以是无参的。

下面代码定义一个Program类,然后在类中加一个入口点方法,在入口点方法中调用Console.WriteLine输出字符串。

            CodeTypeDeclaration t = new CodeTypeDeclaration("Program");

            // 入口点方法
CodeEntryPointMethod main = new CodeEntryPointMethod();
t.Members.Add(main);
// 调用一个方法
CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(typeof(Console)), nameof(Console.WriteLine));
CodeMethodInvokeExpression invoke = new CodeMethodInvokeExpression(mref, new CodePrimitiveExpression("hello pig"));
main.Statements.Add(new CodeExpressionStatement(invoke));

生成的代码如下图所示。

如果要将生成的代码编译为.exe的话,入口点是必须的,否则会“呵呵”的。

-------------------------------------------------------------------------------------

好,喝了3杯稻花香,下面咱们继续,现在该来生成一下事件成员了。

下面代码单独生成了一个 Click 事件。

            CodeMemberEvent ev = new CodeMemberEvent();
ev.Attributes = MemberAttributes.Public;
ev.Name = "Click";
ev.Type = new CodeTypeReference(typeof(System.EventHandler)); CodeDomProvider p = CodeDomProvider.CreateProvider("cs");
p.GenerateCodeFromMember(ev, Console.Out, null);

注意,事件的类型是委托,因此,Type属性所指定的类型必须是委托类型,虽然在代码生成阶段你可以乱写,但是,如果事件的类型不是委托,在编译的时候,编译器会给你脸色看的。所以,咱们还是别太任性了,该指定什么类型就什么类型。

生成的事件如下:

大家想必也知道,在.net类库中有个事件委托叫 EventHandler<TEventArgs>,这个委托的好处使得我们不必要去为各种自定义事件声明委托,一般来说,TEventArgs指代的是 EventArgs 类或者它的子类。

下面我们就用这带有泛型参数的事件委托来生成事件定义代码。

先上代码,大伙看完代码后我再讲解一下。

            CodeNamespace ns = new CodeNamespace("TestSomething");
ns.Imports.Add(new CodeNamespaceImport(nameof(System))); // 自定义事件参数类
CodeTypeDeclaration custEvarg = new CodeTypeDeclaration("SubmitEventArgs");
ns.Types.Add(custEvarg);
// 基类
custEvarg.BaseTypes.Add(new CodeTypeReference(typeof(EventArgs)));
// 字段
CodeMemberField dtf = new CodeMemberField(typeof(byte), "m_data");
custEvarg.Members.Add(dtf);
dtf.Attributes = MemberAttributes.Private;
// 属性
CodeMemberProperty dtpry = new CodeMemberProperty();
custEvarg.Members.Add(dtpry);
dtpry.Name = "Data";
dtpry.Attributes = MemberAttributes.Public | MemberAttributes.Final;
dtpry.Type = new CodeTypeReference(typeof(byte));
// 只读
dtpry.HasGet = true;
dtpry.HasSet = false;
// 返回属性值
CodeMethodReturnStatement ret = new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), dtf.Name));
dtpry.GetStatements.Add(ret);
// 构造函数
CodeConstructor ctor = new CodeConstructor();
custEvarg.Members.Add(ctor);
ctor.Attributes = MemberAttributes.Public;
ctor.Parameters.Add(new CodeParameterDeclarationExpression(typeof(byte), "data"));
ctor.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), dtf.Name), new CodeVariableReferenceExpression("data"))); /**************************************************************/
CodeTypeDeclaration testtype = new CodeTypeDeclaration("Test");
ns.Types.Add(testtype);
// 事件
CodeMemberEvent eve = new CodeMemberEvent();
testtype.Members.Add(eve);
eve.Name = "OnSubmit";
eve.Attributes = MemberAttributes.Public;
eve.Type = new CodeTypeReference($"EventHandler`1[{custEvarg.Name}]"); CodeDomProvider p = CodeDomProvider.CreateProvider("cs");
p.GenerateCodeFromNamespace(ns, Console.Out, null);

代码是蛮长的,不过相信有一部分你是看得懂的。

首先,声明了一个从EventArgs类派生的类,叫SubmitEventArgs。

            CodeTypeDeclaration custEvarg = new CodeTypeDeclaration("SubmitEventArgs");
custEvarg.BaseTypes.Add(new CodeTypeReference(typeof(EventArgs)));

然后加了个私有字段,名为m_data,类型为byte,可以通过Data返回。这个字段的值可以通过传给构造函数参数的值来设置。

第二个类是Test类,它有一个公共事件OnSubmit,这里才是代码的重点,我再复制一遍。

            CodeMemberEvent eve = new CodeMemberEvent();
testtype.Members.Add(eve);
eve.Name = "OnSubmit";
eve.Attributes = MemberAttributes.Public;
eve.Type = new CodeTypeReference($"EventHandler`1[{custEvarg.Name}]");

这里比较难处理的是事件的委托类型,因为我们用的是 EventHandler<TEventArgs>,它带有一个泛型参数,并且泛型参数类型是我们前面要生成的 SubmitEventArgs 类。那么,带泛型参数的类型的完整名字怎么输呢,它的格式是这样的:

<类型名字>`<泛型参数个数>[<类型列表>,……n]

如上面代码中,EventHandler 类有1个泛型参数,所以后面的数字是1,即EventHandler`1,而泛型的类型就紧跟其后,用中括号包起来,表示是一个数组。

比如,某类名叫 Order,它有2个泛型参数,那么 Order<byte, int>的写法是:

Order`[System.Byte, System.Int32]

如果类型要写上程序集、版本号之类的信息,就再套一层中括号,把类型括起来。比如

Order`[[System.Byte,mscorelib,Version=4.0.0.0,PublicKeyToken=xxxxxx], [System.Int32,mscorelib,Version=4.0.0.0]]

如果类型有4个泛型参数,就写成

Order`[……]

总之类型名称后的`之后的数字就是泛型参数的个数,中括号是类型列表。

如果你不知道怎么写,告诉你一招,很简单,不用记,获取类型的Type,再看看它的FullName属性就知道了。至于那个“`”字符,就在键盘左上角,数字1左边那个。看图

最终生成的代码如下图所示。

怎么样,刺激吧。

--------------------------------------------------------------------------------------

下面说一个让不少刚接触 CodeDom 的朋友相当头痛的东东——枚举类型怎么加成员。

你就把枚举的成员当成是字段来处理就OK了,看一个例子。

            CodeTypeDeclaration entype = new CodeTypeDeclaration("CloseMode");
// 指定它是枚举类型
entype.IsEnum = true;
// 添加成员
CodeMemberField m1 = new CodeMemberField();
m1.Name = "Restart";
CodeMemberField m2 = new CodeMemberField();
m2.Name = "Shutdown";
CodeMemberField m3 = new CodeMemberField();
m3.Name = "None";
entype.Members.Add(m1);
entype.Members.Add(m2);
entype.Members.Add(m3); CodeDomProvider prd = CodeDomProvider.CreateProvider("cs");
prd.GenerateCodeFromType(entype, Console.Out, null);

这个枚举有三个成员,对于枚举,字段的类型可以不设置,只设置成员名字就行了,反正你想啊,枚举的成员默认是int,顶多也就是byte、short这些,都是整型的,所以,只要有成员名字就可以了。

生成结果如下。

如果要想为某些成员赋特定数值,可以设置 InitExpression 属性。比如这样

            CodeMemberField m1 = new CodeMemberField();
m1.Name = "Restart";
m1.InitExpression = new CodePrimitiveExpression();

这样,生成的枚举成员就有数值了。

好好,今天老周给大伙介绍了类型成员代码的生成,基本覆盖了属性、字段、方法和事件。就讲这么多吧,讲太多了大家不好消化。

改天有空,老周继续跟大伙聊 CodeDom。

【.net 深呼吸】细说CodeDom(5):类型成员的更多相关文章

  1. 【.net 深呼吸】细说CodeDom(4):类型定义

    上一篇文章中说了命名空间,你猜猜接下来该说啥.是了,命名空间下面就是类型,知道了如何生成命名空间的定义代码,之后就该学会如何声明类型了. CLR的类型通常有这么几种:类.接口.结构.枚举.委托.是这么 ...

  2. 【.net 深呼吸】细说CodeDom(7):索引器

    在开始正题之前,先补充一点前面的内容. 在方法中,如果要引用方法参数,前面的示例中,老周使用的是 CodeVariableReferenceExpression 类,它用于引用变量,也适用于引用方法参 ...

  3. 【.net 深呼吸】细说CodeDom(1):结构大观

    CodeDom 是啥东东?Html Dom听过吧,XML Dom听过吧.DOM一般可翻译为 文档对象模型,那 Code + DOM呢,自然是指代码文档模型了.如果你从来没接触过 CodeDom,你大概 ...

  4. F2工作流引擎参与者类型成员的交、并、互拆计算规则

          计算描述:计算规则指的是和其它“参与者类型成员”的之间的计算,必须求解处理人不为空的情况下才进行规则计算,各个“参与者类型成员”按序号顺序执行. 计算算法:并集(权重最低),交集(权重中) ...

  5. NET设计规范二:类型成员设计

    http://www.cnblogs.com/yangcaogui/archive/2012/04/20/2459567.html 接着 → .NET设计规范一:设计规范基础 上一篇,我们来了解下类型 ...

  6. C++ static、const和static const类型成员变量声明以及初始化

    C++ static.const和static const 以及它们的初始化 const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间. sta ...

  7. 【.net 深呼吸】细说CodeDom(6):方法参数

    本文老周就给大伙伴们介绍一下方法参数代码的生成. 在开始之前,先补充一下上一篇烂文的内容.在上一篇文章中,老周检讨了 MemberAttributes 枚举的用法,老周此前误以为该枚举不能进行按位操作 ...

  8. 【.net 深呼吸】细说CodeDom(3):命名空间

    在上一篇文章中,老周介绍了表达式和语句,尽管老周没有把所有的内容都讲一遍,但相信大伙至少已经掌握基本用法.在本文中,咱们继续探讨 CodeDom 方面的奥秘,这一次咱们聊聊命名空间. 在开始之前,老周 ...

  9. 【.net 深呼吸】细说CodeDom(9):动态编译

    知道了如果构建代码文档,知道了如何生成代码,那么编译程序集就很简单了. CodeDomProvider 类提供了三个可以执行编译的方法: 1.CompileAssemblyFromSource——这个 ...

随机推荐

  1. 用php做注册审核

    做注册审核就像前面讲的注册登录一样,也是要连接数据库 首先在数据库内要做这样一张表: 表名为users表 里面的列名分别为用户名,密码,姓名,性别,生日,账户的状态,照片 然后就可以写代码了,要注册的 ...

  2. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  3. 阿里签名中URLEncode于C#URLEncod不同之处

    问题 如上图所示,阿里云的PercentEncode 转换! 为 %21 PercentEncode 源码为: package com.aliyuncs.auth; import java.io.Un ...

  4. 【Win 10 应用开发】启动远程设备上的应用

    这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...

  5. 《JavaScript设计模式 张》整理

    最近在研读另外一本关于设计模式的书<JavaScript设计模式>,这本书中描述了更多的设计模式. 一.创建型设计模式 包括简单工厂.工厂方法.抽象工厂.建造者.原型和单例模式. 1)简单 ...

  6. 《你不知道的JavaScript》整理(四)——原型

    一.[[Prototype]] JavaScript中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用. var myObject = { a: 2 }; myObje ...

  7. 玩转spring boot——开篇

    很久没写博客了,而这一转眼就是7年.这段时间并不是我没学习东西,而是园友们的技术提高的非常快,这反而让我不知道该写些什么.我做程序已经有十几年之久了,可以说是彻彻底底的“程序老炮”,至于技术怎么样?我 ...

  8. 关于font-family

    在设置页面字体的时候,你会发现在 font-family 属性中会设置好多个字体,想看懂它们都是什么字体吗?不好意思,我不是搞设计的,我也不知道.那么,现在写的东西,只是对于一个前端人员来说,要了解的 ...

  9. CSS 3学习——box-sizing和背景

    box-sizing 在CSS 2中设置元素的width和height仅仅是设置了元素内容区的宽和高,元素实际的尺寸是margin + border + padding + 内容区. CSS 3(截止 ...

  10. Windows API 设置窗口下控件Enable属性

    参考页面: http://www.yuanjiaocheng.net/webapi/create-crud-api-1-put.html http://www.yuanjiaocheng.net/we ...