由C++转向C#需要注意的问题 (3)
2010年06月02日
在网络上读取文件
在C++中,在网络上读取文件需要有相当的编程技巧,.NET对此提供了广泛的支持。事实上,在网络上读取文件仅仅是基础类库中Stream类的另一种应用。
首先,为了对TCP/IP端口(在本例中是65000)进行监听,我们需要创建一个TCPListener类的实例。
TCPListenertcpListener=newTCPListener(65000);
一旦创建后,就让它开始进行监听。
tcpListener.Start();
现在就要等待客户连接的要求了。
SocketsocketForClient=tcpListener.Accept();
TCPListener对象的Accept方法返回一个Socket对象,Accept是一个同步的方法,除非接收到一个连接请求它才会返回。如果连接成功,就可以开始向客户发送文件了。
if(socketForClient.Connected)
{
???
接下来,我们需要创建一个NetworkStream类,将报路传递给constructor:
NetworkStreamnetworkStream=newNetworkStream(socket ForClient);
然后创建一个StreamWriter对象,只是这次不是在文件上而是在刚才创建的NetworkStream类上创建该对象:
System.IO.StreamWriterstreamWriter=
newSystem.IO.StreamWriter(networkStream);
当向该流写内容时,流就通过网络被传输给客户端。
客户端的创建
客户端软件就是一个TCPClient类的具体例子,TCPClient类代表连向主机的一个TCP/IP连接。
TCPClientsocketForServer;
socketForServer=newTCPClient("localHost",65000);
有了TCPClient对象后,我们就可以创建NetworkStream对象了,然后在其上创建StreamReader类:
NetworkStreamnetworkStream=socketForServer.GetStre am();
System.IO.StreamReaderstreamReader=
newSystem.IO.StreamReader(networkStream);
现在,只要其中有数据就读取该流,并将结果输出到控制台上。
do
{
outputString=streamReader.ReadLine();
if(outputString!=null)
{
Console.WriteLine(outputString);
}
}
while(outputString!=null);
为了对这一段代码进行测试,可以创建如下一个测试用的文件:
Thisislineone
Thisislinetwo
Thisislinethree
Thisislinefour
这是来自服务器的输出:
Output(Server)
Clientconnected
SendingThisislineone
SendingThisislinetwo
SendingThisislinethree
SendingThisislinefour
Disconnectingfromclient...
Exiting...
下面是来自客户端的输出:
Thisislineone
Thisislinetwo
Thisislinethree
Thisislinefour
属性和元数据
C#和C++之间一个显著的区别是它提供了对元数据的支持:有关类、对象、方法等其他实体的数据。属性可以分为二类:一类以CLR的一部分的形式出现,另一种是我们自己创建的属性,CLR属性用来支持串行化、排列和COM协同性等。一些属性是针对一个组合体的,有些属性则是针对类或界面,它们也被称作是属性目标。
将属性放在属性目标前的方括号内,属性就可以作用于它们的属性目标。
[assembly:AssemblyDelaySign(false)]
[assembly:AssemblyKeyFile(".\\keyFile.snk")]
或用逗号将各个属性分开:
[assembly:AssemblyDelaySign(false),
assembly:AssemblyKeyFile(".\\keyFile.snk")]
自定义的属性
我们可以任意创建自定义属性,并在认为合适的时候使用它们。假设我们需要跟踪bug的修复情况,就需要建立一个包含bug的数据库,但需要将bug报告与专门的修正情况绑定在一块儿,则可能在代码中添加如下所示的注释:
//Bug323fixedbyJesseLiberty1/1/2005.
这样,在源代码中就可以一目了然地了解bug的修正情况,但如果如果把相关的资料保存在数据库中可能会更好,这样就更方便我们的查询工作了。如果所有的bug报告都使用相同的语法那就更好了,但这时我们就需要一个定制的属性了。我们可能使用下面的内容代替代码中的注释:
[BugFix(323,"JesseLiberty","1/1/2005")Comment="Off byoneerror"]
与C#中的其他元素一样,属性也是类。定制化的属性类需要继承System.Attribute:
publicclassBugFixAttribute:System.Attribute
我们需要让编译器知道这个属性可以跟什么类型的元素,我们可以通过如下的方式来指定该类型的元素:
[AttributeUsage(AttributeTargets.ClassMembers,Allo wMultiple=true)]
AttributeUsage是一个作用于属性的属性━━元属性,它提供的是元数据的元数据,也即有关元数据的数据。在这种情况下,我们需要传递二个参数,第一个是目标(在本例中是类成员。),第二个是表示一个给定的元素是否可以接受多于一个属性的标记。AllowMultiple的值被设置为true,意味着类成员可以有多于一个BugFixAttribute属性。如果要联合二个属性目标,可以使用OR操作符连接它们。
[AttributeUsage(AttributeTargets.Class|AttributeTa rgets.Interface,AllowMultiple=true)]
上面的代码将使一个属性隶属于一个类或一个界面。
新的自定义属性被命名为BugFixAttribute。命名的规则是在属性名之后添加Attribute。在将属性指派给一个元素后,编译器允许我们使用精简的属性名调用这一属性。因此,下面的代码是合法的:
[BugFix(123,"JesseLiberty","01/01/05",Comment="Off byone")]
编译器将首先查找名字为BugFix的属性,如果没有发现,则查找BugFixAttribute。
每个属性必须至少有一个构造器。属性可以接受二种类型的参数:环境参数和命名参数。在前面的例子中,bugID、编程人员的名字和日期是环境参数,注释是命名参数。环境参数被传递到构造器中的,而且必须按在构造器中定义的顺序传递。
publicBugFixAttribute(intbugID,stringprogrammer,st ringdate)
{
this.bugID=bugID;
this.programmer=programmer;
this.date=date;
}
Namedparametersareimplementedasproperties.
属性的使用
为了对属性进行测试,我们创建一个名字为MyMath的简单类,并给它添加二个函数,然后给它指定bugfix属性。
[BugFixAttribute(121,"JesseLiberty","01/03/05")]
[BugFixAttribute(107,"JesseLiberty","01/04/05",
Comment="Fixedoffbyoneerrors")]
publicclassMyMath
这些数据将与元数据存储在一起。下面是完整的源代码及其输出:
自定义属性
usingSystem;
//创建被指派给类成员的自定义属性
[AttributeUsage(AttributeTargets.Class,
AllowMultiple=true)]
publicclassBugFixAttribute:System.Attribute
{
//位置参数的自定义属性构造器
publicBugFixAttribute
(intbugID,
stringprogrammer,
stringdate)
{
this.bugID=bugID;
this.programmer=programmer;
this.date=date;
}
publicintBugID
{
get
{
returnbugID;
}
}
//命名参数的属性
publicstringComment
{
get
{
returncomment;
}
set
{
comment=value;
}
}
publicstringDate
{
get
{
returndate;
}
}
publicstringProgrammer
{
get
{
returnprogrammer;
}
}
//专有成员数据
privateintbugID;
privatestringcomment;
privatestringdate;
privatestringprogrammer;
}
//把属性指派给类
[BugFixAttribute(121,"JesseLiberty","01/03/05")]
[BugFixAttribute(107,"JesseLiberty","01/04/05",
Comment="Fixedoffbyoneerrors")]
publicclassMyMath
{
publicdoubleDoFunc1(doubleparam1)
{
returnparam1+DoFunc2(param1);
}
publicdoubleDoFunc2(doubleparam1)
{
returnparam1/3;
}
}
publicclassTester
{
publicstaticvoidMain()
{
MyMathmm=newMyMath();
Console.WriteLine("CallingDoFunc(7).Result:{0}",
mm.DoFunc1(7));
}
}
输出:
CallingDoFunc(7).Result:9.3333333333333339
象我们看到的那样,属性对输出绝对没有影响,创建属性也不会影响代码的性能。到目前为止,读者也只是在听我论述有关属性的问题,使用ILDASM浏览元数据,就会发现属性确实是存在的。
映射
在许多情况下,我们需要一种方法,能够从元数据中访问属性,C#提供了对映射的支持以访问元数据。通过初始化MemberInfo类型对象,System.Reflection名字空间中的这个对象可以用来发现成员的属性,对元数据进行访问。
System.Reflection.MemberInfoinf=typeof(MyMath);
对MyMath类型调用typeof操作符,它返回一个由继承MemberInfo而生成的Type类型的变量。
下一步是对MemberInfo对象调用GetCustomAttributes,并将希望得到的属性的类型作为一个参数传递给GetCustomAttributes。我们将得到一个对象数组,数组的每个成员的类型都是BugFixAttribute。
object[]attributes;
attributes=Attribute.GetCustomAttributes(inf,typeo f(BugFixAttribute));
我们就可以遍历这个数组了,打印BugFixAttribute对象的数组,代码下所示:
属性的打印
publicstaticvoidMain()
{
MyMathmm=newMyMath();
Console.WriteLine("CallingDoFunc(7).Result:{0}",
mm.DoFunc1(7));
//获取成员信息并使用它访问自定义的属性
System.Reflection.MemberInfoinf=typeof(MyMath);
object[]attributes;
attributes=
Attribute.GetCustomAttributes(inf,typeof(BugFixAtt ribute));
//遍历所有的属性
foreach(Objectattributeinattributes)
{
BugFixAttributebfa=(BugFixAttribute)attribute;
Console.WriteLine("\nBugID:{0}",bfa.BugID);
Console.WriteLine("Programmer:{0}",bfa.Programmer) ;
Console.WriteLine("Date:{0}",bfa.Date);
Console.WriteLine("Comment:{0}",bfa.Comment);
} }
类型发现
我们可以通过映象的方法来研究一个组合实体的内容,如果要建立需要显示组合体内部信息的工具或动态地调用组合体中的途径,这一方法是非常有用的。
通过映象的方法,我们可以知道一个模块、方法、域、属性的类型,以及该类型的每个方法的信号、该类支持的界面和该类的超级类。我们可以通过如下的形式,用Assembly.Load静态方法动态地加载一个组合体:
publicstaticAssembly.Load(AssemblyName)
然后,可以将它传递到核心库中。
Assemblya=Assembly.Load("Mscorlib.dll");
一旦加载了组合体,我们可以通过调用GetTypes返回一个Type对象数组。Type对象是映射的核心,它表示类、界面、数组、值和枚举等的类型定义。
Type[]types=a.GetTypes();
组合休会返回一个类型的数组,我们可以使用foreach-loop结构显示该数组,其输出将有好几页文档之多,下面我们从中找一小段:
TypeisSystem.TypeCode
TypeisSystem.Security.Util.StringExpressionSet
TypeisSystem.Text.UTF7Encoding$Encoder
TypeisSystem.ArgIterator
TypeisSystem.Runtime.Remoting.JITLookupTable
1205typesfound
我们得到了一个内容为核心库中类型的数组,可以将它们都打印出来,该数组将有1205个项。
对一种类型映射我们也可以对组合体中一种类型进行映射。为此,我们可以使用GetType方法从组合体中解析出一个类型:
publicclassTester
{
publicstaticvoidMain()
{
//检查一个对象
TypetheType=Type.GetType("System.Reflection.Assemb ly");
Console.WriteLine("\nSingleTypeis{0}\n",theType);
}
}
输出如下所示:
SingleTypeisSystem.Reflection.Assembly
发现成员
我们还可以得到所有成员的类型,显示所有的方法、属性、域,下面的代码演示了实现上述目标的代码。
Figure9GettingAllMembers
publicclassTester
{
publicstaticvoidMain()
{
//检查一个单一的对象
TypetheType=Type.GetType("System.Reflection.Assemb ly");
Console.WriteLine("\nSingleTypeis{0}\n",theType);
//获取所有的成员
MemberInfo[]mbrInfoArray=
theType.GetMembers(BindingFlags.LookupAll);
foreach(MemberInfombrInfoinmbrInfoArray)
{
Console.WriteLine("{0}isa{1}",
mbrInfo,mbrInfo.MemberType.Format());
} }
}
尽管得到的输出还非常长,但在输出中我们可以得到如下面的不甘落后民示的域、方法、构造器和属性:
System.Strings_localFilePrefixisaField
BooleanIsDefined(System.Type)isaMethod
Void.ctor()isaConstructor
System.StringCodeBaseisaProperty
System.StringCopiedCodeBaseisaProperty
只发现方法
我们可能会只关心方法,而不关心域、属性等,为此,我们需要删除如下的对GetMembers的调用:
MemberInfo[]mbrInfoArray=
theType.GetMembers(BindingFlags.LookupAll);
然后添加调用GetMethods的语句:
mbrInfoArray=theType.GetMethods();
现在,输出中就只剩下方法了。
Output(excerpt)
BooleanEquals(System.Object)isaMethod
System.StringToString()isaMethod
System.StringCreateQualifiedName(System.String,Sys tem.String)
isaMethod
System.Reflection.MethodInfoget_EntryPoint()isaMet hod
发现特定的成员
最后,为了进一步地缩小范围,我们可以使用FindMembers方法来发现某一类型的特定的方法。例如,在下面的代码中,我们可以只搜索以"Get"开头的方法。
publicclassTester
{
publicstaticvoidMain()
{
//检查一个单一的对象
TypetheType=Type.GetType("System.Reflection.Assemb ly");
//只获取以Get开头的成员
MemberInfo[]mbrInfoArray
theType.FindMembers(MemberTypes.Method,
BindingFlags.Default,
Type.FilterName,"Get*");
foreach(MemberInfombrInfoinmbrInfoArray)
{
Console.WriteLine("{0}isa{1}",
mbrInfo,mbrInfo.MemberType.Format());
} }
}
其输出的一部分如下所示:
System.Type[]GetTypes()isaMethod
System.Type[]GetExportedTypes()isaMethod
System.TypeGetType(System.String,Boolean)isaMethod
System.TypeGetType(System.String)isaMethod
System.Reflection.AssemblyNameGetName(Boolean)isaM ethod
System.Reflection.AssemblyNameGetName()isaMethod
Int32GetHashCode()isaMethod
System.Reflection.AssemblyGetAssembly(System.Type) isaMethod
System.TypeGetType(System.String,Boolean,Boolean)i saMethod
动态调用
一旦发现一个方法,可以使用映射的方法调用它。例如,我们可能需要调用System.Math中的Cos方法(返回一个角的余弦值)。为此,我们需要获得System.Math类的类型信息,如下所示:
TypetheMathType=Type.GetType("System.Math");
有了类型信息,我们就可以动态地加载一个类的实例:
ObjecttheObj=Activator.CreateInstance(theMathType) ;
CreateInstance是Activator类的一个静态方法,可以用来对对象进行初始化。
有了System.Math类的实例后,我们就可以调用Cos方法了。我们还需要准备好一个定义参数类型的数组,因为Cos只需要一个参数(需要求余弦值的角度),因此数组中只需要有一个成员。我们将在数组中赋予一个System.Double类型的Type对象,也就是Cos方法需要的参数的类型:
Type[]paramTypes=newType[1];
paramTypes[0]=Type.GetType("System.Double");
现在我们就可以传递方法的名字了,这个数组定义了Type对象中GetMethod方法的参数的类型:
MethodInfoCosineInfo=
theMathType.GetMethod("Cos",paramTypes);
我们现在得到了MethodInfo类型的对象,我们可以在其上调用相应的方法。为此,我们需要再次在数组中传入参数的实际值:
Object[]parameters=newObject[1];
parameters[0]=45;
ObjectreturnVal=CosineInfo.Invoke(theObj,parameter s);
需要注意的是,我创建了二个数组,第一个名字为paramTypes的数组存储着参数的类型,第二个名字为parameters的数组保存实际的参数值。如果方法需要二个参数,我们就需要使这二个数组每个保持二个参数。如果方法不需要参数,我们仍然需要创建这二个数组,只是无需在里面存储数据即可。
Type[]paramTypes=newType[0];
尽管看起来有点奇怪,但它是正确的。下面是完整的代码:
映射方法的使用
usingSystem;
usingSystem.Reflection;publicclassTester
{
publicstaticvoidMain()
{
TypetheMathType=Type.GetType("System.Math");
ObjecttheObj=Activator.CreateInstance(theMathType) ;
//只有一个成员的数组
Type[]paramTypes=newType[1];
paramTypes[0]=Type.GetType("System.Double");
//获得Cos()方法的信息
MethodInfoCosineInfo=
theMathType.GetMethod("Cos",paramTypes);
//将实际的参数填写在一个数组中
Object[]parameters=newObject[1];
parameters[0]=45;
ObjectreturnVal=CosineInfo.Invoke(theObj,parameter s);
Console.WriteLine(
"Thecosineofa45degreeangle{0}",returnVal);
} }
结论
尽管有许多小错误等着C++编程人员去犯,但C#的语法与C++并没有太大的不同,向新语言的转换是相当容易的。使用C#的有趣的部分是使用通用语言运行库,这篇文章只能涉及几个重点问题。CLR和.NETFramework提供了对线程、集合、互联网应用开发、基于Windows的应用开发等方面提供了更多的支持。语言功能和CLR功能之间的区分是非常模糊的,但组合在一起就是一种功能非常强大的开发工具了。
2010年06月02日
在网络上读取文件
在C++中,在网络上读取文件需要有相当的编程技巧,.NET对此提供了广泛的支持。事实上,在网络上读取文件仅仅是基础类库中Stream类的另一种应用。
首先,为了对TCP/IP端口(在本例中是65000)进行监听,我们需要创建一个TCPListener类的实例。
TCPListenertcpListener=newTCPListener(65000);
一旦创建后,就让它开始进行监听。
tcpListener.Start();
现在就要等待客户连接的要求了。
SocketsocketForClient=tcpListener.Accept();
TCPListener对象的Accept方法返回一个Socket对象,Accept是一个同步的方法,除非接收到一个连接请求它才会返回。如果连接成功,就可以开始向客户发送文件了。
if(socketForClient.Connected)
{
???
接下来,我们需要创建一个NetworkStream类,将报路传递给constructor:
NetworkStreamnetworkStream=newNetworkStream(socket ForClient);
然后创建一个StreamWriter对象,只是这次不是在文件上而是在刚才创建的NetworkStream类上创建该对象:
System.IO.StreamWriterstreamWriter=
newSystem.IO.StreamWriter(networkStream);
当向该流写内容时,流就通过网络被传输给客户端。
客户端的创建
客户端软件就是一个TCPClient类的具体例子,TCPClient类代表连向主机的一个TCP/IP连接。
TCPClientsocketForServer;
socketForServer=newTCPClient("localHost",65000);
有了TCPClient对象后,我们就可以创建NetworkStream对象了,然后在其上创建StreamReader类:
NetworkStreamnetworkStream=socketForServer.GetStre am();
System.IO.StreamReaderstreamReader=
newSystem.IO.StreamReader(networkStream);
现在,只要其中有数据就读取该流,并将结果输出到控制台上。
do
{
outputString=streamReader.ReadLine();
if(outputString!=null)
{
Console.WriteLine(outputString);
}
}
while(outputString!=null);
为了对这一段代码进行测试,可以创建如下一个测试用的文件:
Thisislineone
Thisislinetwo
Thisislinethree
Thisislinefour
这是来自服务器的输出:
Output(Server)
Clientconnected
SendingThisislineone
SendingThisislinetwo
SendingThisislinethree
SendingThisislinefour
Disconnectingfromclient...
Exiting...
下面是来自客户端的输出:
Thisislineone
Thisislinetwo
Thisislinethree
Thisislinefour
属性和元数据
C#和C++之间一个显著的区别是它提供了对元数据的支持:有关类、对象、方法等其他实体的数据。属性可以分为二类:一类以CLR的一部分的形式出现,另一种是我们自己创建的属性,CLR属性用来支持串行化、排列和COM协同性等。一些属性是针对一个组合体的,有些属性则是针对类或界面,它们也被称作是属性目标。
将属性放在属性目标前的方括号内,属性就可以作用于它们的属性目标。
[assembly:AssemblyDelaySign(false)]
[assembly:AssemblyKeyFile(".\\keyFile.snk")]
或用逗号将各个属性分开:
[assembly:AssemblyDelaySign(false),
assembly:AssemblyKeyFile(".\\keyFile.snk")]
自定义的属性
我们可以任意创建自定义属性,并在认为合适的时候使用它们。假设我们需要跟踪bug的修复情况,就需要建立一个包含bug的数据库,但需要将bug报告与专门的修正情况绑定在一块儿,则可能在代码中添加如下所示的注释:
//Bug323fixedbyJesseLiberty1/1/2005.
这样,在源代码中就可以一目了然地了解bug的修正情况,但如果如果把相关的资料保存在数据库中可能会更好,这样就更方便我们的查询工作了。如果所有的bug报告都使用相同的语法那就更好了,但这时我们就需要一个定制的属性了。我们可能使用下面的内容代替代码中的注释:
[BugFix(323,"JesseLiberty","1/1/2005")Comment="Off byoneerror"]
与C#中的其他元素一样,属性也是类。定制化的属性类需要继承System.Attribute:
publicclassBugFixAttribute:System.Attribute
我们需要让编译器知道这个属性可以跟什么类型的元素,我们可以通过如下的方式来指定该类型的元素:
[AttributeUsage(AttributeTargets.ClassMembers,Allo wMultiple=true)]
AttributeUsage是一个作用于属性的属性━━元属性,它提供的是元数据的元数据,也即有关元数据的数据。在这种情况下,我们需要传递二个参数,第一个是目标(在本例中是类成员。),第二个是表示一个给定的元素是否可以接受多于一个属性的标记。AllowMultiple的值被设置为true,意味着类成员可以有多于一个BugFixAttribute属性。如果要联合二个属性目标,可以使用OR操作符连接它们。
[AttributeUsage(AttributeTargets.Class|AttributeTa rgets.Interface,AllowMultiple=true)]
上面的代码将使一个属性隶属于一个类或一个界面。
新的自定义属性被命名为BugFixAttribute。命名的规则是在属性名之后添加Attribute。在将属性指派给一个元素后,编译器允许我们使用精简的属性名调用这一属性。因此,下面的代码是合法的:
[BugFix(123,"JesseLiberty","01/01/05",Comment="Off byone")]
编译器将首先查找名字为BugFix的属性,如果没有发现,则查找BugFixAttribute。
每个属性必须至少有一个构造器。属性可以接受二种类型的参数:环境参数和命名参数。在前面的例子中,bugID、编程人员的名字和日期是环境参数,注释是命名参数。环境参数被传递到构造器中的,而且必须按在构造器中定义的顺序传递。
publicBugFixAttribute(intbugID,stringprogrammer,st ringdate)
{
this.bugID=bugID;
this.programmer=programmer;
this.date=date;
}
Namedparametersareimplementedasproperties.
属性的使用
为了对属性进行测试,我们创建一个名字为MyMath的简单类,并给它添加二个函数,然后给它指定bugfix属性。
[BugFixAttribute(121,"JesseLiberty","01/03/05")]
[BugFixAttribute(107,"JesseLiberty","01/04/05",
Comment="Fixedoffbyoneerrors")]
publicclassMyMath
这些数据将与元数据存储在一起。下面是完整的源代码及其输出:
自定义属性
usingSystem;
//创建被指派给类成员的自定义属性
[AttributeUsage(AttributeTargets.Class,
AllowMultiple=true)]
publicclassBugFixAttribute:System.Attribute
{
//位置参数的自定义属性构造器
publicBugFixAttribute
(intbugID,
stringprogrammer,
stringdate)
{
this.bugID=bugID;
this.programmer=programmer;
this.date=date;
}
publicintBugID
{
get
{
returnbugID;
}
}
//命名参数的属性
publicstringComment
{
get
{
returncomment;
}
set
{
comment=value;
}
}
publicstringDate
{
get
{
returndate;
}
}
publicstringProgrammer
{
get
{
returnprogrammer;
}
}
//专有成员数据
privateintbugID;
privatestringcomment;
privatestringdate;
privatestringprogrammer;
}
//把属性指派给类
[BugFixAttribute(121,"JesseLiberty","01/03/05")]
[BugFixAttribute(107,"JesseLiberty","01/04/05",
Comment="Fixedoffbyoneerrors")]
publicclassMyMath
{
publicdoubleDoFunc1(doubleparam1)
{
returnparam1+DoFunc2(param1);
}
publicdoubleDoFunc2(doubleparam1)
{
returnparam1/3;
}
}
publicclassTester
{
publicstaticvoidMain()
{
MyMathmm=newMyMath();
Console.WriteLine("CallingDoFunc(7).Result:{0}",
mm.DoFunc1(7));
}
}
输出:
CallingDoFunc(7).Result:9.3333333333333339
象我们看到的那样,属性对输出绝对没有影响,创建属性也不会影响代码的性能。到目前为止,读者也只是在听我论述有关属性的问题,使用ILDASM浏览元数据,就会发现属性确实是存在的。
映射
在许多情况下,我们需要一种方法,能够从元数据中访问属性,C#提供了对映射的支持以访问元数据。通过初始化MemberInfo类型对象,System.Reflection名字空间中的这个对象可以用来发现成员的属性,对元数据进行访问。
System.Reflection.MemberInfoinf=typeof(MyMath);
对MyMath类型调用typeof操作符,它返回一个由继承MemberInfo而生成的Type类型的变量。
下一步是对MemberInfo对象调用GetCustomAttributes,并将希望得到的属性的类型作为一个参数传递给GetCustomAttributes。我们将得到一个对象数组,数组的每个成员的类型都是BugFixAttribute。
object[]attributes;
attributes=Attribute.GetCustomAttributes(inf,typeo f(BugFixAttribute));
我们就可以遍历这个数组了,打印BugFixAttribute对象的数组,代码下所示:
属性的打印
publicstaticvoidMain()
{
MyMathmm=newMyMath();
Console.WriteLine("CallingDoFunc(7).Result:{0}",
mm.DoFunc1(7));
//获取成员信息并使用它访问自定义的属性
System.Reflection.MemberInfoinf=typeof(MyMath);
object[]attributes;
attributes=
Attribute.GetCustomAttributes(inf,typeof(BugFixAtt ribute));
//遍历所有的属性
foreach(Objectattributeinattributes)
{
BugFixAttributebfa=(BugFixAttribute)attribute;
Console.WriteLine("\nBugID:{0}",bfa.BugID);
Console.WriteLine("Programmer:{0}",bfa.Programmer) ;
Console.WriteLine("Date:{0}",bfa.Date);
Console.WriteLine("Comment:{0}",bfa.Comment);
} }
类型发现
我们可以通过映象的方法来研究一个组合实体的内容,如果要建立需要显示组合体内部信息的工具或动态地调用组合体中的途径,这一方法是非常有用的。
通过映象的方法,我们可以知道一个模块、方法、域、属性的类型,以及该类型的每个方法的信号、该类支持的界面和该类的超级类。我们可以通过如下的形式,用Assembly.Load静态方法动态地加载一个组合体:
publicstaticAssembly.Load(AssemblyName)
然后,可以将它传递到核心库中。
Assemblya=Assembly.Load("Mscorlib.dll");
一旦加载了组合体,我们可以通过调用GetTypes返回一个Type对象数组。Type对象是映射的核心,它表示类、界面、数组、值和枚举等的类型定义。
Type[]types=a.GetTypes();
组合休会返回一个类型的数组,我们可以使用foreach-loop结构显示该数组,其输出将有好几页文档之多,下面我们从中找一小段:
TypeisSystem.TypeCode
TypeisSystem.Security.Util.StringExpressionSet
TypeisSystem.Text.UTF7Encoding$Encoder
TypeisSystem.ArgIterator
TypeisSystem.Runtime.Remoting.JITLookupTable
1205typesfound
我们得到了一个内容为核心库中类型的数组,可以将它们都打印出来,该数组将有1205个项。
对一种类型映射我们也可以对组合体中一种类型进行映射。为此,我们可以使用GetType方法从组合体中解析出一个类型:
publicclassTester
{
publicstaticvoidMain()
{
//检查一个对象
TypetheType=Type.GetType("System.Reflection.Assemb ly");
Console.WriteLine("\nSingleTypeis{0}\n",theType);
}
}
输出如下所示:
SingleTypeisSystem.Reflection.Assembly
发现成员
我们还可以得到所有成员的类型,显示所有的方法、属性、域,下面的代码演示了实现上述目标的代码。
Figure9GettingAllMembers
publicclassTester
{
publicstaticvoidMain()
{
//检查一个单一的对象
TypetheType=Type.GetType("System.Reflection.Assemb ly");
Console.WriteLine("\nSingleTypeis{0}\n",theType);
//获取所有的成员
MemberInfo[]mbrInfoArray=
theType.GetMembers(BindingFlags.LookupAll);
foreach(MemberInfombrInfoinmbrInfoArray)
{
Console.WriteLine("{0}isa{1}",
mbrInfo,mbrInfo.MemberType.Format());
} }
}
尽管得到的输出还非常长,但在输出中我们可以得到如下面的不甘落后民示的域、方法、构造器和属性:
System.Strings_localFilePrefixisaField
BooleanIsDefined(System.Type)isaMethod
Void.ctor()isaConstructor
System.StringCodeBaseisaProperty
System.StringCopiedCodeBaseisaProperty
只发现方法
我们可能会只关心方法,而不关心域、属性等,为此,我们需要删除如下的对GetMembers的调用:
MemberInfo[]mbrInfoArray=
theType.GetMembers(BindingFlags.LookupAll);
然后添加调用GetMethods的语句:
mbrInfoArray=theType.GetMethods();
现在,输出中就只剩下方法了。
Output(excerpt)
BooleanEquals(System.Object)isaMethod
System.StringToString()isaMethod
System.StringCreateQualifiedName(System.String,Sys tem.String)
isaMethod
System.Reflection.MethodInfoget_EntryPoint()isaMet hod
发现特定的成员
最后,为了进一步地缩小范围,我们可以使用FindMembers方法来发现某一类型的特定的方法。例如,在下面的代码中,我们可以只搜索以"Get"开头的方法。
publicclassTester
{
publicstaticvoidMain()
{
//检查一个单一的对象
TypetheType=Type.GetType("System.Reflection.Assemb ly");
//只获取以Get开头的成员
MemberInfo[]mbrInfoArray
theType.FindMembers(MemberTypes.Method,
BindingFlags.Default,
Type.FilterName,"Get*");
foreach(MemberInfombrInfoinmbrInfoArray)
{
Console.WriteLine("{0}isa{1}",
mbrInfo,mbrInfo.MemberType.Format());
} }
}
其输出的一部分如下所示:
System.Type[]GetTypes()isaMethod
System.Type[]GetExportedTypes()isaMethod
System.TypeGetType(System.String,Boolean)isaMethod
System.TypeGetType(System.String)isaMethod
System.Reflection.AssemblyNameGetName(Boolean)isaM ethod
System.Reflection.AssemblyNameGetName()isaMethod
Int32GetHashCode()isaMethod
System.Reflection.AssemblyGetAssembly(System.Type) isaMethod
System.TypeGetType(System.String,Boolean,Boolean)i saMethod
动态调用
一旦发现一个方法,可以使用映射的方法调用它。例如,我们可能需要调用System.Math中的Cos方法(返回一个角的余弦值)。为此,我们需要获得System.Math类的类型信息,如下所示:
TypetheMathType=Type.GetType("System.Math");
有了类型信息,我们就可以动态地加载一个类的实例:
ObjecttheObj=Activator.CreateInstance(theMathType) ;
CreateInstance是Activator类的一个静态方法,可以用来对对象进行初始化。
有了System.Math类的实例后,我们就可以调用Cos方法了。我们还需要准备好一个定义参数类型的数组,因为Cos只需要一个参数(需要求余弦值的角度),因此数组中只需要有一个成员。我们将在数组中赋予一个System.Double类型的Type对象,也就是Cos方法需要的参数的类型:
Type[]paramTypes=newType[1];
paramTypes[0]=Type.GetType("System.Double");
现在我们就可以传递方法的名字了,这个数组定义了Type对象中GetMethod方法的参数的类型:
MethodInfoCosineInfo=
theMathType.GetMethod("Cos",paramTypes);
我们现在得到了MethodInfo类型的对象,我们可以在其上调用相应的方法。为此,我们需要再次在数组中传入参数的实际值:
Object[]parameters=newObject[1];
parameters[0]=45;
ObjectreturnVal=CosineInfo.Invoke(theObj,parameter s);
需要注意的是,我创建了二个数组,第一个名字为paramTypes的数组存储着参数的类型,第二个名字为parameters的数组保存实际的参数值。如果方法需要二个参数,我们就需要使这二个数组每个保持二个参数。如果方法不需要参数,我们仍然需要创建这二个数组,只是无需在里面存储数据即可。
Type[]paramTypes=newType[0];
尽管看起来有点奇怪,但它是正确的。下面是完整的代码:
映射方法的使用
usingSystem;
usingSystem.Reflection;publicclassTester
{
publicstaticvoidMain()
{
TypetheMathType=Type.GetType("System.Math");
ObjecttheObj=Activator.CreateInstance(theMathType) ;
//只有一个成员的数组
Type[]paramTypes=newType[1];
paramTypes[0]=Type.GetType("System.Double");
//获得Cos()方法的信息
MethodInfoCosineInfo=
theMathType.GetMethod("Cos",paramTypes);
//将实际的参数填写在一个数组中
Object[]parameters=newObject[1];
parameters[0]=45;
ObjectreturnVal=CosineInfo.Invoke(theObj,parameter s);
Console.WriteLine(
"Thecosineofa45degreeangle{0}",returnVal);
} }
结论
尽管有许多小错误等着C++编程人员去犯,但C#的语法与C++并没有太大的不同,向新语言的转换是相当容易的。使用C#的有趣的部分是使用通用语言运行库,这篇文章只能涉及几个重点问题。CLR和.NETFramework提供了对线程、集合、互联网应用开发、基于Windows的应用开发等方面提供了更多的支持。语言功能和CLR功能之间的区分是非常模糊的,但组合在一起就是一种功能非常强大的开发工具了。