引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的。
String、数组、类、接口和委托都是引用类型。
强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一个System.InvalidCastException异常,
而as不会抛出异常,它返回一个null值。
用using创建别名:using console = System.Console;
访问限定符:
public 该成员可以被其他任何类访问
protected 该成员只能被其派生类访问
private 该成员只能被本类的其他成员访问
internal 该成员只能在当前编译单元的其他成员访问
带参数列表和返回值的Main方法:
class Test
{
public static int Main(string[] args)
{
foreach (string arg in args)
{
...
}
}
}
构造函数(constructor)包括实例构造函数和静态构造函数。
构造函数与类名相同,且不能有返回值。例:
class TestClass
{
TestClass() //实例构造函数:可以访问静态成员和实例成员,用于初始化实例成员
{
...
}
static TestClass() //静态构造函数:只能访问静态成员,用于初始化静态成员
{
...
}
}
类的静态成员属于类所有,不必生成实例就可以访问,它是在载入包含类的应用程序时创建的,
但静态方法不能访问类的实例变量和方法。通常,静态变量是在定义时就赋初始值的。
类的实例成员属于类的实例所有,不创建实例对象就无法对其进行访问,实例成员可以访问类的
静态成员和其它实例成员。
调用基类的析构函数:
class A
{
public A()
{
...
}
}
class B
{
public B(): base() //调用基类的析构函数
{
...
}
}
常量:其值是在编译时设定的,必须是数值文字。默认状态下常量是静态的。例:
class A
{
public const double pi = 3.1415;
}
常量是编译时就确定的值,只读字段是在运行才能确定的值。比如运行时才能确定的屏幕分辨率。
只读字段只能在类的析构函数中赋值。
静态只读字段:
class A
{
public static readonly int ScreenWidth; //静态只读字段
static A() //静态析构函数
{
ScreenWidth = 1024; //在静态析构函数中初始化
}
}
在类的继承中,类的析构函数是不会被继承的。
一个派生类只能从一个基类继承,不能同时从多个基类继承,但可以通过继承多个接口来
达到相同目的。实现多继承的唯一方法就是使用接口。例:
class MyFancyGrid: Control, ISerializable, IDataBound
{
...
}
密封类是不能继承的类,抽象类不能被定义为密封类,且密封类的私有成员不能用protected修饰,
只能用private。例:
sealed class A
{
...
}
关键字ref和out用于指定用引用方式传递方法的参数。
它们的区别是:ref参数必须初始化,而out参数不需要初始化。所以在方法处理代码依赖参数的
初始化值时使用ref,不依赖初始化值时使用out。
对out参数即使在传递前对其进行了初始化,其值也不会传递到方法处理函数内部。传递时系统
会将其设为未初始化。所以在方法内部必须对out参数进行初始化。
方法重载时,必须参数数目和参数类型其中之一不同,返回值不同不能作为重载。
C#不支持方法的默认值,只能通过方法重载来实现。例:
class A
{
int Method(int a)
{
...
}
void Method(int a, int b) //参数数目不同
{ //返回值不同不能作为重载
...
}
}
params参数用于一个不定数目参数的方法,一般后面跟一个数组。例:
class A
{
public void Method(params int[] i)
{
...
}
}
方法的覆盖:指派生类覆盖基类的同名方法,有二种方法
1)第一种是在派生类要覆盖的方法前面加new修饰,而基类不需要作任何改动。
这种方法的缺点是不能实现多态。例:
class A
{
public void Method() //无需任何修饰
{
...
}
}
class B: A //从基类继承
{
new public void Method() //覆盖基类的同名方法
{
...
}
}
class TestClass
{
A Instance = new B();
Instance.Method(); //这时将调用类A的Method方法,而不是类B的Method方法
}
2)第二种是在派生类要覆盖的方法前面加override修饰,而基类的同名方法前面加virtual修饰。
这样就能实现多态,例:
class A
{
virtual public void Method() //基类定义虚方法
{ //虚拟方法不能定义为private,因为private成员对派生类是无法访问的
...
}
}
class B: A //从基类继承
{
override public void Method() //派生类覆盖基类的同名虚方法
{
...
}
}
class TestClass
{
protected void Test()
{
A Instance = new B(); //定义一个实例,类型为基类,从派生类创建
//派生类总是能够向上转换为其基类
Instance.Method(); //将调用派生类B的Method方法,而不是基类的,这就是多态
}
}
说明:new修饰的方法覆盖不能实现多态的原因,是因为使用new时编译器只会实现早期绑定(early binding)。
即调用的方法在编译时就决定了:编译器看到Instance.Method()而Instance的类是A,就会调用类A的Method()方法。
override修饰的方法覆盖可以实现多态的原因,是因为实现了后期绑定(late binding)。
使用override时强制编译器在运行时根据类的真正类型正确调用相应的方法,而不是在编译时。
而基类的同名方法必须加virtual修饰。
类的静态方法可能通过 类名.静态方法名 这种格式来调用,不能使用 实例名.静态方法名 这种方法调用。
因为类的静态方法为类所有(是属于类本身的),而非实例所有(不是属于类的实例的)。
类的静态方法可以访问类的任何静态成员,但不能访问类的实例成员。
C#中类的变量称为字段。类的public变量称为类的公共字段。
类的属性由一个protected(也可以是private)字段和getter和setter方法构成:
class Address
{
protected string zipCode; //protected字段,注意大小写
public string ZipCode
{
get //getter方法
{
return zipCode;
}
set //setter方法
{
zipCode = value; //被传递的值自动被在这个value变量中
}
};
}
只读属性是指省略setter方法的属性,只读属性只能读取,不能设置。
属性也可以用限定符virtual,override和abstract修饰,功能同其他类的方法。
属性有一个用处称为懒惰的初始化(lazy initialization)。即在需要类成员时才对它们进行
初始化。如果类中包含了很少被引用的成员,而这些成员的初始化又会花费大量的时候和系统
资源的话,懒惰的初始化就很有用了。
C#中数组对象共同的基类是System.Array。
将数组声明为类的一个成员时,声明数组与实例化数组必须分开,这是因为只能在运行时创建了
类的实例对象之后,才能实例化数组元素值。
声明:
int[] intArray; //一维数组
int[,,] int3Array; //三维数组
初始化:
intArray = new int[3] {1,2,3};
int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}}; //声明时可以初始化
遍历:
1)一维数组
for (int i = 0; i < intArray.Length; i++); //Array.Length返回数组所有元素的个数
foreach (int i in intArray);
for (int i = 0; i < intArray.GetLength (0); i++);//Array.GetLength(0)返回数组第一维的个数
2)多维数组
for (int i = 0; i < int3Array.GetLength(0); i++) //遍历三维数组
for (int j = 0; j < int3Array.GetLength(1); j++)
for (int k = 0; k < int3Array.GetLength (2); k++)
{
...
}
数组的维数就是该数组的秩(Rank)。Array.Rank可以返回数据的秩。
锯齿数组(jagged Array)是元素为数组的数组,例:
int[][] jaggedArray = new int[2][]; //包含二个元素,每个元素是个数组
jaggedArray[0] = new int[2]; //每个元素必须初始化
jaggedArray[1] = new int[3];
for (int i = 0; i < jaggedArray.Length; i++) //遍历锯齿数组
for (int j = 0; j < jaggedArray[i].Length; j++)
{
...
}
类的属性称为智能字段,类的索引器称为智能数组。由于类本身作数组使用,所以用
this作索引器的名称,索引器有索引参数值。例:
using System;
using System.Collections;
class MyListBox
{
protected ArrayList data = new ArrayList();
public object this[int idx] //this作索引器名称,idx是索引参数
{
get
{
if (idx > -1 && idx < data.Count)
{
return data[idx];
}
else
{
return null;
}
}
set
{
if (idx > -1 && idx < data.Count)
{
data[idx] = value;
}
else if (idx = data.Count)
{
data.Add(value);
}
else
{
//抛出一个异常
}
}
}
}
接口是二段不同代码之间约定,通过约定实现彼此之间的相互访问。
C#并不支持多继承,但通过接口可实现相同功能。
当在接口中指定了实现这个接口的类时,我们就称这个类"实现了该接口"或"从接口继承"。
一个接口基本上就是一个抽象类,这个抽象类中除了声明C#类的其他成员类型——例如属性、
事件和索引器之外,只声明了纯虚拟方法。
接口中可以包含方法、属性、索引器和事件——其中任何一种都不是在接口自身中来实现的。例:
interface IExampleInterface
{
//property declaration
int testProperty { get; }
//event declaration
event testEvevnt Changed;
//mothed declaration
function void testMothed();
//indexer declaration
string this[int index] { get; set; }
}
说明:定义接口时,在方法、属性、事件和索引器所有这些接口成员都不能用public之类的访问限定符,
因为所有接口成员都是public类型的。
因为接口定义了一个约定,任何实现一个接口的类都必须定义那个接口中每一个成员,否则将编译失败。例:
using System;
public class FancyControl
{
protected string data;
public string Data
{
get {return this.data;}
set {data = value;}
}
}
interface IValidate
{
bool Validate(); //接口方法
}
public class MyControl: FancyControl, IValidate
{
public MyControl()
{
data = "my control data";
}
public bool Validate() //实现接口
{
if (data == "my control data")
return true;
else
return false;
}
}
class InterfaceApp
{
MyControl myControl = new MyControl();
IValidate val = (IValidate)myControl; //可以将一个实现某接口的类,转换成该接口
bool success = val.Validate(); //然后可调用该接口的方法
}
也可以用:
bool success = myControl.Validate();
这种方法来调用Validate方法,因为Validate在类MyControl中是被定义成public的,如果去除public,Validate方法被隐藏,
就不能用这种方法调用了,这样隐藏接口方法称为名字隐藏(name hiding)。
可以用:类实例 is 接口名 来判断某个类是否实现了某接口,例:
myControl is IValidate //MyControl类的实例myControl是否实现了IValidate接口
当然,也可用as来作转换,根据转换结果是否为null来判断某个类是否实现了某接口,例:
IValidate val = myControl as IValidate;
if (null == val)
{
... //没有实现IValidate接口
}
else
{
... //实现了IValidate接口
}
如果一个类从多个接口继承,而这些接口中如果定义的同名的方法,则实现接口的方法时,必须加接口名来区别,
写成 接口名.方法名。假设Test类从IDataStore和ISerializable二个接口继承,而这二个接口都有SaveData()方法,
实现SaveData()方法时必须写成:
class Test: ISerializable, IDataStore
{
void ISerializable.SaveData()
{
...
}
void IDataStore.SaveData ()
{
...
}
}
如果一个类从多个接口继承,为了方便可以定义一个新的接口,这个接口继续多个接口,然后类直接从这个接口继承就
可以了,这个叫合并接口。例:
interface ISaveData: ISerializable, IDataStore
{ //不需要定义任何方法或成员,只是用作合并
}
class Test: ISaveData //只要继承ISaveData就可以了
{
...
}
C# 操作符优先级(从高到低)
初级操作符 () x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
一元操作符 + - | ~ ++x --x (T)x
乘除操作符 * / %
加减操作符 + -
位移操作符 << >>
关系操作符 < > <= >= is
等于操作符 ==
逻辑与 &
逻辑异或 ^
逻辑或 |
条件与 &&
条件或 ||
条件操作符 ?:
赋值操作符 = *= /= %= += -= <<= >>= &= ^= |=
所有的二元操作符除赋值符外都是左联合的,即从左到右计算。
typeof()运算符可以从一个类名得到一个System.Type对象,而从System.Object对象继承来的GetType ()方法
则可从一个类实例来得到一个System.Type对象。例:
Type t1 = typeof(Apple); //Apple是一个类名
Apple apple = new Apple(); //apple是Apple类的一个实例
Type t2 = apple.GetType(); //t1与t2是相同的
通过反射得到一个类的所有成员和方法:
Type t = typeof(Apple);
string className = t.ToString(); //得到类名
MethodInfo[] methods = t.GetMethods(); //得到所有方法
foreach (MethodInfo method in methods)
{
//用method.ToString()得到方法名
}
MemberInfo[] members = t.GetMembers(); //得到所有成员
foreach (MemberInfo member in members)
{
//用member.ToString()得到成员名
}
sizeof()操作符用来计算值类型变量在内存中占用的字节数(Bytes),并且它只能在unsafe(非安全)
代码中使用。例:
static unsafe public void ShowSizes()
{
int i, j;
j = sizeof(short);
j = sizeof(i);
}
尽可能使用复合赋值操作符,它比不用复合赋值操作符的效率高。
for语句的语法为:
for (initialization; Boolean-expression; step)
embedded-statement
在initialization和step部份还可以使用逗号操作符,例:
for (int i = '0', j = 1; i <= '\xFF'; i++, j++)
for (int i = 1, j = 1; i < 1000; i += j, j = i - j) //输出斐波那契数列
Console.Write("{0} ", i);
在switch语句中执行一个分支的代码后还想执行另一个分支的代码,可以用:
goto case 分支;
操作符重载是为了让程序更加自然,容易理解。想要为一个类重新定义一个操作符,使用以下语法:
public static 返回值 operator 操作符 (操作对象1[,操作对象2])
说明:
1)所有重载的操作符方法都必须定义为public和static
2)从技术上说返回值可以是任何类型,但通常是返回所定义方法使用的类型
3)操作对象的数目取决于重载是一元操作符还是二元操作符,一元操作符只要一个操作对象,二元操作符则需要二个。
4)不管重载是一元操作符还是二元操作符,第一个操作对象的类型都必须与返回值的类型一致;而对于二元操作符的第二个
操作对象的类型则可以是任何类型。
5)只有下列操作符可以被重载:
一元:+ - ! ~ ++ -- true false
二元:+ - * / % & | ^ << >> == != > < >= <=
赋值操作符(+=,-=,*-,/=,%=等等)无法被重载。
[]和()操作符也无法被重载。
6)操作符的优先级是无法改变的,运算优先级的规则是静态的。
例:假设一个Invoice发票类由多个InvoiceDetailLine类(成员只有一个Double类型的Amount金额属性)组成,
我们重载+操作符,使之可以将InvoiceDetailLine类的内容(注意不是金额合计)加在一起。
class Invoice
{
public ArrayList DetailLine;
public Invoice //类的析构函数
{
DetailLine = new ArrayList(); //ArrayList存放多个InvoiceDetailLine类的实例
}
public static Invoice operator+ (Invoice Invoice1, Invoice Invoice2) //参数与返回值的类型一致
{
//Invoice1与Invoice2的内容合并
Invoice ReturnInvoice = new Invoice();
foreach(InvoiceDetailLine detailLine in Invoice1.DetailLines)
ReturnInvoice.DetailLine.Add(detailLine);
foreach(InvoiceDetailLine detailLine in Invoice2.DetailLines)
ReturnInvoice.DetailLine.Add(detailLine);
return ReturnInvoice;
}
}
class InvoiceAddApp //调用示例
{
public static void main()
{
Invoice i1 = new Invoice();
for(int i = 0; i < 3; i++)
i1.DetailLine.Add(new InvoiceDetailLine(i + 1));
Invoice i2 = new Invoice();
for(int i = 0; i < 3; i++)
i2.DetailLine.Add(new InvoiceDetailLine(i + 1));
Invoice summaryInvoice = i1 + i2; //调用重载的操作符+方法
}
}
自定义类型转换可以编写代码实际二个不同的类、结构体之间的转换。
语法:public static implicite/explicite operator 输出类型 (输入类型)
说明:
1)转换方法必须是静态的。
2)implicite表示隐式转换,explicite表示显式转换。
3)输入类型和输出类型其中之一必须与包含转换的类或结构体类型。即转换必须与本类相关。
例:
struct Celisus
{
public float t;
public Celisus(float t)
{
this.t = t; //this.t是结构体的字段,t是参数
}
public static implicite operator Celisus(float t) //float=>Celisus
{
return new Celisus(t);
}
public static implicite operator float(Celisus c) //Celisus=>float
{
return ((c.t - 32) / 9) * 5;
}
}
代表的(delegate)目的与C++中的函数指针相同,代表不是在编译时被定义的,而是在运行时被定义的。
代表主要有二个用途:回调(Callback)和事件处理(event)
回调通常用于异步处理和自定义处理。例:
class DBManager
{
static DBConnection[] activeConnections;
//声明回调函数
public void delegate EnumConnectionCallback(DBConnection connection);
public static void EnumConnections(EnumConnectionCallback callback)
{
foreach (DBConnection connection in activeConnections)
{
callback(connection); //执行回调函数
}
}
}
//调用
class DelegateApp
{
public static void ActiveConncetionCallback(DBConnection connection) //处理函数
{
...
}
public void main()
{
//创建指向具体处理函数的代表实例(新建一个代表,让它指向具体的处理函数)
DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
DBManager.EnumConnections (myCallback);
}
}
//使用静态代表,上面的调用改为
class DelegateApp
{
//创建一个指向处理函数的静态代表
public static DBManager.EmnuConnectionCallback myCallback
= new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
public static void ActiveConncetionCallback(DBConnection connection)
{
...
}
public void main()
{
DBManager.EnumConnections(myCallback);
}
}
//在需要时才创建代表,上面的调用改为
class DelegateApp
{
//将创建代表放在属性的getter方法中
public static DBManager.EmnuConnectionCallback myCallback
{
get
{
retun new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
}
}
public static void ActiveConncetionCallback(DBConnection connection)
{
...
}
public void main()
{
DelegateApp app = new DelegateApp(); //创建应用程序
DBManager.EnumConnections (myCallback);
}
}
可以将多个代表整合成单个代表,例:
class CompositeDelegateApp
{
public static void LogEvent(Part part)
{
...
}
public static void EmailPurchasingMgr(Part part)
{
...
}
public static void Main()
{
//定义二个代表
InventoryManager.OutOfStockExceptionMethod LogEventCallback
= new InventoryManager.OutOfStockExceptionMethod(LogEvent);
InventoryManager.OutOfStockExceptionMethod EmailPurchasingMgrCallback
= new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
//整合为一个代表,注意后加的代表先执行(这里是先执行LogEventCallback)
InventoryManager.OutOfStockExceptionMethod onHandExceptionEventsCallback
= EmailPurchasingMgrCallback + LogEventCallback;
//调用代表
InventoryManager mgr = new InventoryManager();
mgr.ProcessInventory(onHandExceptionEventsCallback);
//InventoryManager类的ProcessInventory方法的原型为:
//public void ProcessInventory(OutOfStockExceptionMethod exception);
}
}
可以根据需要将多个代表自由地组合成单个代表,例:
class CompositeDelegateApp
{
//代表指向的处理函数(三个代表三个函数)
public static void LogEvent(Part part)
{
...
}
public static void EmailPurchasingMgr(Part part)
{
...
}
public static void EmailStoreMgr(Part part)
{
...
}
public static void Main()
{
//通过数组定义三个代表
InventoryManager.OutOfStockExceptionMethod[] exceptionMethods
= new InventoryManager.OutOfStockExceptionMethod[3];
exceptionMethods[0] = new InventoryManager.OutOfStockExceptionMethod (LogEvent);
exceptionMethods[1] = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
exceptionMethods[2] = new InventoryManager.OutOfStockExceptionMethod(EmailStoreMgr);
int location = 1;
//再定义一个代表(用于组合成单代表)
InventoryManager.OutOfStockExceptionMethod compositeDelegate;
//根据需要组合
if (location = 2)
{
compositeDelegate = exceptionMethods[0] + exceptionMethods[1];
}
else
{
compositeDelegate = exceptionMethods[0] + exceptionMethods[2];
}
//调用代表
InventoryManager mgr = new InventoryManager();
mgr.ProcessInventory(compositeDelegate);
}
}
C#的事件遵循"发布——预订"的设计模式。在这种模式中,一个类公布能够出现的所有事件,
然后任何的类都可以预订这些事件。一旦事件产生,运行环境就负责通知每个订户事件已经发生了。
当代表作为事件的处理结果时(或者说定义具有代表的事件),定义的代表必须指向二个参数的方法:
一个参数是引发事件的对象(发布者),另一个是事件信息对象(这个对象必须从EventArgs类中派生)。
例:
using System;
class InventoryChangeEventArgs: EventArgs //事件信息对象,从EventArgs类派生
{
... //假设定义二个public属性string Sku和int Change
}
class InventoryManager //事件的发布者
{
//声明代表
public delegate void InventoryChangeEventHander(object source, InventoryChangeEventArgs e);
//发布事件,event关键字可将一个代表指向多个处理函数
public event InventoryChangeEventHandler onInventoryChangeHander;
public void UpdateInventory(string sku, int change)
{
if (change == 0)
return;
InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change);
//触发事件
if (onInventoryChangeHandler != null) //如果有预订者就触发
onInventoryChangeHandler(this, e); //执行代表指向的处理函数
}
}
class InventoryWatcher //事件的预订者
{
public InventoryWatcher(InventoryManager mgr) //mgr参数用于联结发布者
{
this.inventoryManager = mgr;
//预订事件,用 += 调用多个处理函数
mgr.onInventroyChangeHandler += new InventoryManager.InventoryChangeEventHandler (onInventoryChange);
//事件处理函数
void onInventroyChange(object source, InventroyChangeEventArgs e)
{
...
}
InventoryManager inventoryManager;
}
}
class EventsApp //主程序
{
public static void Main()
{
InventoryManager inventoryManager = new InventoryManager();
InventoryWatcher inventoryWatcher = new InventoryWatcher(inventoryManager);
inventoryManager.UpdateInventory ("111 006 116", -2);
inventoryManager.UpdateInventory("111 006 116", 5);
}
}
Microsoft Windows NT和IBM OS/2等操作系统都支持占先型多任务。在占先型多任务执行中,处理器负责
给每个线程分配一定量的运行时间——一个时间片(timeslice)。处理器接着在不同的线程之间进行切换,
执行相应的处理。在单处理器的计算机上,并不能真正实现多个线程的同时运行,除非运行在多个处理器
的计算机上。操作系统调度的多线程只是根据分配给每个线程时间片进行切换执行,感觉上就像同时执行。
上下文切换(context switching)是线程运行的一部分,处理器使用一个硬件时间来判断一个指定线程的时间片
何时结束。当这个硬件计时器给出中断信号时,处理器把当前运行的线程所用的所有寄存器(registers)数据
存储到堆栈中。然后,处理器把堆栈里那些相同的寄存器信息存放到一种被称为"上下文结构"的数据结构中。
当处理器要切换回原来执行的线程时,它反向执行这个过程,利用与该线程相关的上下文结构,在寄存器里
重新恢复与这一线程相关的信息。这样的一个完整过程称为"上下文切换"。
多线程允许应用程序把任务分割为多个线程,它们彼此之间可以独立地工作,最大限度地利用了处理器时间。
using System;
using System.Threading;
class SimpleThreadApp
{
public static void WorkerThreadMethod() //线程的执行体
{
... //执行一些操作
}
public static void Main()
{
//创建一个线程代表指向线程的执行体,ThreadStart是创建新线程必须用到的代表
ThreadStart worker = new ThreadStart(WorkerThreadMethod);
Thread t = new Thread(worker); //用线程代表创建线程
t.Start(); //执行线程
}
}
可以通过两种方式来得到一个Thread对象:一种是通过创建一个新线程来得到,如上例;另一种在正在执行的线程调用
静态的Thread.CurrentThread方法。
静态方法Thread.Sleep(int ms)可以让当前线程(它自动调用Thread.CurrentThread)暂停指定毫秒的时间。
如果使用Thread.Sleep(0)那么当前线程将一直处于等待中,直到另一个线程调用这个线程的实例方法Thread.Interrupt方法,
等待才会结束。
使用Thread.Suspend方法也能挂起线程,Thread.Suspend 方法可以被当前线程或其他线程调用,而Thread.Sleep(0)
只能由当前线程在执行体中调用。当线程用Thread.Suspend挂起时,必须用Thread.Resume方法恢复。不论Thread.Suspend
方法调用了多少次,只要调用Thread.Resume方法一次就可以线程恢复执行。用Thread.Suspend方法并不会阻塞线程,
调用立即返回。而Thread.Sleep(0)则会阻塞线程。所以确切地说Thread.Sleep (0)暂停线程,而不是挂起线程。
使用Thread.Abort方法可以终止正在执行的线程。当Thread.Abort方法被调用时,线程不会立即终止执行。运行环境将会
等待,直到线程到达文档中所描述的"安全点"。如果要确保线程已经完全停止,可以使用Thread.Join方法。这是一个同步
调用,同步调用意味着直到线程完全停止,调用才会返回。
Thread.Priority属性用于设置的线程的优先级。其值是Thread.ThreadPriority枚举值,可以设为Highest, AboveNormal,
Normal, BelowNormal, Lowest。缺省值是Thread.ThreadPriority.Normal。
线程的同步是为了解决多个线程同时使用同一对象产生的一些问题。通过同步,可以指定代码的临界区(critical section),
一次只有一个线程可以进入临界区。
使用System.Monitor类(锁定与信号量)进行线程同步:
using System;
using System.Threading;
public void SaveData(string text) //线程执行函数或线程执行函数调用的对象的方法
{
... //执行其他一些不需要同步的处理
Monitor.Enter(this); //获取对象的Monitor锁
... //执行需要同步的处理
Monitor.Exit(this); //释放对象的Monitor锁
... //执行其他一些不需要同步的处理
}
说明:当执行Monitor.Enter方法时。这个方法会试图获取对象上的Monitor锁,如果另一个线程已经拥有了
这个锁,这个方法将会阻塞(block),直到这个锁被释放。
也可用C#的lock语句来获得和释放一个Monitor锁。上面同步写成:
public void SaveData(string text) //线程执行函数或线程执行函数调用的对象的方法
{
... //执行其他一些不需要同步的处理
lock(this) //获取对象的Monitor锁,代码块执行完成后释放Monitor锁
{
... //执行需要同步的处理
}
... //执行其他一些不需要同步的处理
}
也可以使用System.Threading名称空间的Mutex类(互斥类)进行线程同步。与Monitor锁一样,一次只有一个线程
能获得一个给定的互斥。但Mutex要慢得多,但它增加了灵活性。例:
using System;
using System.Threading;
class Database
{
Mutex mutex = new Mutex(false); //创建一个互斥,但不立即获得它
//注意:创建互斥在需要同步的方法之外,实际上它只要创建一个实例
public void SaveData(string text) //需要同步的方法
{
mutex.WaitOne(); //等待获得互斥
... //需要同步的处理
mntex.Close(); //释放互斥
}
}
Mutex类重载了三个构造函数:
Mutex() //创建并使创建类立即获得互斥
Mutex(bool initiallyOwned) //创建时可指定是否要立即获得互斥
Mutex(bool initiallyOwned, string muterName) //还可以指定互斥的名称
Mutex.WaitOne方法也重载了三次:
Mutex.WaitOne() //一直等待
Mutex.WaitOne(TimeSpan time, bool exitContext) //等待TimeSpan指定的时间
Mutex.WaitOne(int milliseconds, bool exitContext) //等待指定的毫秒
线程的用法:
1)并发操作:比如一个程序监视多个COM口,当每个COM接到信息时执行一段处理时。
2)复杂长时间操作:一个长时间的复杂操作可能会使界面停滞,停止用户响应,如果还允许用户停止它,
或者显示进度条、显示操作执行进程信息时。
反射(Reflection)就是能够在运行时查找类型信息,这是因为.NET编译的可执行(PE)文件中包括MSIL和元数据(metadata)。
反射的中心是类System.Type。System.Type是一个抽象类,代表公用类型系统(Common Type System, CTS)中的一种类型。
using System;
using System.Reflection; //反射命名空间,必须引用
public static void Main(string[] args)
{
int i = 6;
Type t = i.GetType(); //根据实例得到类型
t = Type.GetType("System.Int32"); //根据类型的字符名称得到类型
}
通过Assembly类可以得到已经编译.NET Framework程序的中所有类型,例:
using System;
using System.Diagnostics; //为了使用Process类
using System.Reflection; //为了使用Assembly类
class GetTypesApp
{
protected static string GetAssemblyName(string[] args)
{
string assemblyName;
if (0 == args.Length) //如果参数为空,取当前进程的名称
{
Process p = Process.GetCurrentProcess();
assemblyName = p.ProcessName + ".exe";
}
else
assemblyName = args[0]; //取第一个参数,即当前运行程序名
return assemblyName;
}
public static void Main(string[] args)
{
string assemblyName = GetAssemblyName(args);
Assembly a = Assembly.LoadFrom (assemblyName); //调用编译程序集
Type[] types = a.GetTypes(); //得到多个类型
foreach (Type t in types) //遍历类型数组
{
... //取得t.FullName,t.BaseType.FullName等类型信息
}
}
}
一个应用程序可以包括多个代码模块。若要将一个cs文件编译一个模块,只要执行下面的命令:
csc /target:module 要编译的模块.cs //csc是C Sharp Compiler(C#编译器)
然后在应用程序中using编译的模块.cs中的NameSpace即可应用了。
要反射应用程序中所有代码模块(Module),只要:
Assembly a = Assembly.LoadFrom(assemblyName); //应用程序的物理文件名
Module[] modules = a.GetModules ();
foreach(Module m in modules)
{
... //显示m.Name等
}
后期绑定(latebinding),例:
string[] fileNames = Directory.GetFiles(Environment.CurrentDirectory, "*.dll");
foreach (string fileName in fileNames)
{
Assembly a = Assembly.LoadFrom(fileName);
Type[] types = a.GetTypes();
foreach(Type t in types)
{
if (t.IsSubclassOf(typeof(CommProtocol))) //判断是否有CommProtocol的派生类
{
object o = Activator.CreateInstance(t); //生成实例
MethodInfo mi = t.GetMethod("DisplayName");
mi.Invoke(o, null); //调用方法
}
}
}
//带参数的例子
namespace Programming_CSharp
{
using System;
using System.Reflection;
public class Tester
{
public static void Main( )
{
Type t = Type.GetType("System.Math");
Object o = Activator.CreateInstance(t);
// 定义参数类型
Type[] paramTypes = new Type[1];
paramTypes[0]= Type.GetType("System.Double");
MethodInfo CosineInfo = t.GetMethod("Cos", paramTypes);
//设置参数数据
Object[] parameters = new Object[1];
parameters[0] = 45;
//执行方法
Object returnVal = CosineInfo.Invoke(o, parameters);
Console.WriteLine("The cosine of a 45 degree angle {0}", returnVal);
}
}
}
动态生成代码和动态调用的完整例子:
//动态生成代码的部分
using System;
using System.Reflection;
using System.Reflection.Emit; //动态生成代码必须引用
namespace ILGenServer
{
public class CodeGenerator
{
public CodeGenerator()
{
currentDomain = AppDomain.CurrentDomain; //得到当前域
assemblyName = new AssemblyName(); //从域创建一个程序集
assemblyName.Name = "TempAssembly";
//得到一个动态编译生成器, AssemblyBuilerAccess.Run表示只在内存中运行,不能保存
assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilerAccess.Run);
//从编译生成器得到一个模块生成器
moduleBuilder = assemblyBuilder.DefineDynamicModule ("TempModule");
//模块生成器得到类生成器
typeBuilder = moduleBuilder.DefineType("TempClass", TypeAttributes.Public);
//为类添加一个方法
methodBuilder = typeBuilder.DefineMethod("HelloWord", MethodAttributes.Public, null, null);
//为方法写入代码,生成代码必须使用到IL生成器
msil = methodBuilder.GetILGenerator();
msil.EmitWriteLine("Hello World");
msil.Emit(OpCodes.Ret);
//最后还需要编译(build)一下类
t = typeBuilder.CreateType();
}
AppDomain currentDomain;
AssemblyName assemblyName;
AssemblyBuilder assemblyBuilder;
ModuleBuilder moduleBuilder;
TypeBuilder typeBuilder;
MethodBuilder methodBuilder;
ILGenerator msil;
object o;
Type t;
public Type T
{
get
{
return this.t;
}
}
}
}
//动态调用的部分
using System;
using System.Reflection;
using ILGenServer; //引用动态生成代码的类
public class ILGenClientApp
{
public static void Main(
{
CodeGenerator gen = new CodeGenerator(); //创建动态生成类
Type t = gen.T;
if (null != t)
{
object o = Activator.CreateInstance(t);
MethodInfo helloWorld = t.GetMethod("HelloWorld"); //为调用方法创建一个MethodInfo
if (null != helloWorld)
{
helloWorld.Invoke(o, null); //调用方法
}
}
}
}
调用DLL
using System;
using System.Runtime.InteropServices; //为了使用DLLImport特性
class PInvokeApp
{
[DllImport("user32.dll ", CharSet=CharSet.Ansi)] //CharSet.Ansi指定Ansi版本的函数(MessageBoxA),CharSet.Unicode指定Unicode版本的函数(MessageBoxW)
static extern int MessageBox(int hWnd, string msg, string caption, int type); //声明DLL中的函数
//[DllImport(" user32.dll", EntryPoint="MessageBoxA")] //用这种方法使用不同的函数名
//static extern int MsgBox(int hWnd, string msg, string caption, int type);
//[DllImport("user32.dll", CharSet=CharSet.Unicode )] //调用Unicode版的DLL函数
//static extern int MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]string msg,
// [MarshalAs(UnmanagedType.LPWStr)]string caption, int type); //将LPWStr翻译为string型,缺省情况系统只将LPStr翻译成string
public static void Main()
{
MessageBox(0, "Hello, World!", "CaptionString", 0); //调用DLL中的函数
}
}
例2,使用回调:
class CallbackApp
{
[DllImport("user32.dll")]
static extern int GetWindowText(int hWnd, StringBuilder text, int count);
delegate bool CallbackDef(int hWnd, int lParam);
[DllImport("user32.dll")]
static extern int EnumWindows(CallbackDef callback, int lParam);
static bool PrintWindow(int hWnd, int lParam)
{
StringBuilder text = new StringBuilder(255);
GetWindowText(hWnd, text, 255);
Console.WriteLine("Window Caption: {0}", text);
return true;
}
static void Main()
{
CallbackDef callback = new CallbackDef(PrintWindow);
EnumWindows(callback, 0);
}
}
关键字unsafe指定标记块在非控环境中运行。该关键字可以用于所有的方法,包括构造函数和属性,
甚至还有方法中的代码块。关键字fixed负责受控对象的固定(pinning)。Pinning是一种动作,向
垃圾收集器(Garbage Collector, GC)指定一些不能被移动的对象。为了不在内存中产生碎片,.NET
运行环境把对象四处移动,以便于最有效地利用内存。使用fixed后指定对象将不会被移动,所以就
可以用指针来访问它。
C#中只能得到值类型、数组和字符串的指针。在数组的情况下,第一个元素必须是值类型,因为C#
实际上是返回一个指向数组第一个元素的指针,而不是返回数组自身。
& 取一个变量的内存地址(即指向该变量的指针)
* 取指针所指变量的值
-> 取成员
例:
using System;
class UnsafeApp
{
public static unsafe void Getvalues(int* x, int* y)
{
*x = 6;
*y = 42;
}
public static unsafe void Main()
{
int a = 1;
int b = 2;
Getvalues(&a, &b);
}
}
fixed语法为:fixed(type* ptr = expression) statements
其中type也可以为非控类型,也可是void;expression是任何产生一个type指针的表达式;
statements是应用的代码块。例:
fixed (int* f = &foo.x) //foo是Foo类的一个实例,x是Foo类的一个int属性
{
SetFoovalue(f); //SetFoovalue方法的定义为unsafe static void SetFoovalue(int* x)
}
传统的COM组件可以通过互操作层(COM Interop)与.NET运行环境交互。互操作层处理在托管运行环境和非托管区域
中的COM组件操作之间传递所有的消息。
要使COM组件能在.NET环境中使用,必须为COM组件生成元数据。.NET运行环境用元数据层业判断类型信息。在运行时刻
使用类型信息,以便生成RCW(Runtime Callable Wrapper,运行时可调用包装)。当.NET应用程序与COM对象交互时,
RCW处理对COM对象的装载和调用。RCW还完成许多其他的工作,如管理对象标识、对象生存周期以及接口缓冲区。
对象生存周期管理十分关键,因为.NET GC把对象到处移动,并且当对象不再使用时,自动处理这些对象。RCW服务告诉
.NET,应用程序正与托管.NET组件交互,同时又使非托管COM组件"觉得"COM对象是被传统的COM客户端调用的。
为了为COM组件生成元数据包装,必须使用tlbimp.exe(TypeLib Importer)工具:
tlbimp some_COM.tlb /out:som_COM.dll
String、数组、类、接口和委托都是引用类型。
强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一个System.InvalidCastException异常,
而as不会抛出异常,它返回一个null值。
用using创建别名:using console = System.Console;
访问限定符:
public 该成员可以被其他任何类访问
protected 该成员只能被其派生类访问
private 该成员只能被本类的其他成员访问
internal 该成员只能在当前编译单元的其他成员访问
带参数列表和返回值的Main方法:
class Test
{
public static int Main(string[] args)
{
foreach (string arg in args)
{
...
}
}
}
构造函数(constructor)包括实例构造函数和静态构造函数。
构造函数与类名相同,且不能有返回值。例:
class TestClass
{
TestClass() //实例构造函数:可以访问静态成员和实例成员,用于初始化实例成员
{
...
}
static TestClass() //静态构造函数:只能访问静态成员,用于初始化静态成员
{
...
}
}
类的静态成员属于类所有,不必生成实例就可以访问,它是在载入包含类的应用程序时创建的,
但静态方法不能访问类的实例变量和方法。通常,静态变量是在定义时就赋初始值的。
类的实例成员属于类的实例所有,不创建实例对象就无法对其进行访问,实例成员可以访问类的
静态成员和其它实例成员。
调用基类的析构函数:
class A
{
public A()
{
...
}
}
class B
{
public B(): base() //调用基类的析构函数
{
...
}
}
常量:其值是在编译时设定的,必须是数值文字。默认状态下常量是静态的。例:
class A
{
public const double pi = 3.1415;
}
常量是编译时就确定的值,只读字段是在运行才能确定的值。比如运行时才能确定的屏幕分辨率。
只读字段只能在类的析构函数中赋值。
静态只读字段:
class A
{
public static readonly int ScreenWidth; //静态只读字段
static A() //静态析构函数
{
ScreenWidth = 1024; //在静态析构函数中初始化
}
}
在类的继承中,类的析构函数是不会被继承的。
一个派生类只能从一个基类继承,不能同时从多个基类继承,但可以通过继承多个接口来
达到相同目的。实现多继承的唯一方法就是使用接口。例:
class MyFancyGrid: Control, ISerializable, IDataBound
{
...
}
密封类是不能继承的类,抽象类不能被定义为密封类,且密封类的私有成员不能用protected修饰,
只能用private。例:
sealed class A
{
...
}
关键字ref和out用于指定用引用方式传递方法的参数。
它们的区别是:ref参数必须初始化,而out参数不需要初始化。所以在方法处理代码依赖参数的
初始化值时使用ref,不依赖初始化值时使用out。
对out参数即使在传递前对其进行了初始化,其值也不会传递到方法处理函数内部。传递时系统
会将其设为未初始化。所以在方法内部必须对out参数进行初始化。
方法重载时,必须参数数目和参数类型其中之一不同,返回值不同不能作为重载。
C#不支持方法的默认值,只能通过方法重载来实现。例:
class A
{
int Method(int a)
{
...
}
void Method(int a, int b) //参数数目不同
{ //返回值不同不能作为重载
...
}
}
params参数用于一个不定数目参数的方法,一般后面跟一个数组。例:
class A
{
public void Method(params int[] i)
{
...
}
}
方法的覆盖:指派生类覆盖基类的同名方法,有二种方法
1)第一种是在派生类要覆盖的方法前面加new修饰,而基类不需要作任何改动。
这种方法的缺点是不能实现多态。例:
class A
{
public void Method() //无需任何修饰
{
...
}
}
class B: A //从基类继承
{
new public void Method() //覆盖基类的同名方法
{
...
}
}
class TestClass
{
A Instance = new B();
Instance.Method(); //这时将调用类A的Method方法,而不是类B的Method方法
}
2)第二种是在派生类要覆盖的方法前面加override修饰,而基类的同名方法前面加virtual修饰。
这样就能实现多态,例:
class A
{
virtual public void Method() //基类定义虚方法
{ //虚拟方法不能定义为private,因为private成员对派生类是无法访问的
...
}
}
class B: A //从基类继承
{
override public void Method() //派生类覆盖基类的同名虚方法
{
...
}
}
class TestClass
{
protected void Test()
{
A Instance = new B(); //定义一个实例,类型为基类,从派生类创建
//派生类总是能够向上转换为其基类
Instance.Method(); //将调用派生类B的Method方法,而不是基类的,这就是多态
}
}
说明:new修饰的方法覆盖不能实现多态的原因,是因为使用new时编译器只会实现早期绑定(early binding)。
即调用的方法在编译时就决定了:编译器看到Instance.Method()而Instance的类是A,就会调用类A的Method()方法。
override修饰的方法覆盖可以实现多态的原因,是因为实现了后期绑定(late binding)。
使用override时强制编译器在运行时根据类的真正类型正确调用相应的方法,而不是在编译时。
而基类的同名方法必须加virtual修饰。
类的静态方法可能通过 类名.静态方法名 这种格式来调用,不能使用 实例名.静态方法名 这种方法调用。
因为类的静态方法为类所有(是属于类本身的),而非实例所有(不是属于类的实例的)。
类的静态方法可以访问类的任何静态成员,但不能访问类的实例成员。
C#中类的变量称为字段。类的public变量称为类的公共字段。
类的属性由一个protected(也可以是private)字段和getter和setter方法构成:
class Address
{
protected string zipCode; //protected字段,注意大小写
public string ZipCode
{
get //getter方法
{
return zipCode;
}
set //setter方法
{
zipCode = value; //被传递的值自动被在这个value变量中
}
};
}
只读属性是指省略setter方法的属性,只读属性只能读取,不能设置。
属性也可以用限定符virtual,override和abstract修饰,功能同其他类的方法。
属性有一个用处称为懒惰的初始化(lazy initialization)。即在需要类成员时才对它们进行
初始化。如果类中包含了很少被引用的成员,而这些成员的初始化又会花费大量的时候和系统
资源的话,懒惰的初始化就很有用了。
C#中数组对象共同的基类是System.Array。
将数组声明为类的一个成员时,声明数组与实例化数组必须分开,这是因为只能在运行时创建了
类的实例对象之后,才能实例化数组元素值。
声明:
int[] intArray; //一维数组
int[,,] int3Array; //三维数组
初始化:
intArray = new int[3] {1,2,3};
int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}}; //声明时可以初始化
遍历:
1)一维数组
for (int i = 0; i < intArray.Length; i++); //Array.Length返回数组所有元素的个数
foreach (int i in intArray);
for (int i = 0; i < intArray.GetLength (0); i++);//Array.GetLength(0)返回数组第一维的个数
2)多维数组
for (int i = 0; i < int3Array.GetLength(0); i++) //遍历三维数组
for (int j = 0; j < int3Array.GetLength(1); j++)
for (int k = 0; k < int3Array.GetLength (2); k++)
{
...
}
数组的维数就是该数组的秩(Rank)。Array.Rank可以返回数据的秩。
锯齿数组(jagged Array)是元素为数组的数组,例:
int[][] jaggedArray = new int[2][]; //包含二个元素,每个元素是个数组
jaggedArray[0] = new int[2]; //每个元素必须初始化
jaggedArray[1] = new int[3];
for (int i = 0; i < jaggedArray.Length; i++) //遍历锯齿数组
for (int j = 0; j < jaggedArray[i].Length; j++)
{
...
}
类的属性称为智能字段,类的索引器称为智能数组。由于类本身作数组使用,所以用
this作索引器的名称,索引器有索引参数值。例:
using System;
using System.Collections;
class MyListBox
{
protected ArrayList data = new ArrayList();
public object this[int idx] //this作索引器名称,idx是索引参数
{
get
{
if (idx > -1 && idx < data.Count)
{
return data[idx];
}
else
{
return null;
}
}
set
{
if (idx > -1 && idx < data.Count)
{
data[idx] = value;
}
else if (idx = data.Count)
{
data.Add(value);
}
else
{
//抛出一个异常
}
}
}
}
接口是二段不同代码之间约定,通过约定实现彼此之间的相互访问。
C#并不支持多继承,但通过接口可实现相同功能。
当在接口中指定了实现这个接口的类时,我们就称这个类"实现了该接口"或"从接口继承"。
一个接口基本上就是一个抽象类,这个抽象类中除了声明C#类的其他成员类型——例如属性、
事件和索引器之外,只声明了纯虚拟方法。
接口中可以包含方法、属性、索引器和事件——其中任何一种都不是在接口自身中来实现的。例:
interface IExampleInterface
{
//property declaration
int testProperty { get; }
//event declaration
event testEvevnt Changed;
//mothed declaration
function void testMothed();
//indexer declaration
string this[int index] { get; set; }
}
说明:定义接口时,在方法、属性、事件和索引器所有这些接口成员都不能用public之类的访问限定符,
因为所有接口成员都是public类型的。
因为接口定义了一个约定,任何实现一个接口的类都必须定义那个接口中每一个成员,否则将编译失败。例:
using System;
public class FancyControl
{
protected string data;
public string Data
{
get {return this.data;}
set {data = value;}
}
}
interface IValidate
{
bool Validate(); //接口方法
}
public class MyControl: FancyControl, IValidate
{
public MyControl()
{
data = "my control data";
}
public bool Validate() //实现接口
{
if (data == "my control data")
return true;
else
return false;
}
}
class InterfaceApp
{
MyControl myControl = new MyControl();
IValidate val = (IValidate)myControl; //可以将一个实现某接口的类,转换成该接口
bool success = val.Validate(); //然后可调用该接口的方法
}
也可以用:
bool success = myControl.Validate();
这种方法来调用Validate方法,因为Validate在类MyControl中是被定义成public的,如果去除public,Validate方法被隐藏,
就不能用这种方法调用了,这样隐藏接口方法称为名字隐藏(name hiding)。
可以用:类实例 is 接口名 来判断某个类是否实现了某接口,例:
myControl is IValidate //MyControl类的实例myControl是否实现了IValidate接口
当然,也可用as来作转换,根据转换结果是否为null来判断某个类是否实现了某接口,例:
IValidate val = myControl as IValidate;
if (null == val)
{
... //没有实现IValidate接口
}
else
{
... //实现了IValidate接口
}
如果一个类从多个接口继承,而这些接口中如果定义的同名的方法,则实现接口的方法时,必须加接口名来区别,
写成 接口名.方法名。假设Test类从IDataStore和ISerializable二个接口继承,而这二个接口都有SaveData()方法,
实现SaveData()方法时必须写成:
class Test: ISerializable, IDataStore
{
void ISerializable.SaveData()
{
...
}
void IDataStore.SaveData ()
{
...
}
}
如果一个类从多个接口继承,为了方便可以定义一个新的接口,这个接口继续多个接口,然后类直接从这个接口继承就
可以了,这个叫合并接口。例:
interface ISaveData: ISerializable, IDataStore
{ //不需要定义任何方法或成员,只是用作合并
}
class Test: ISaveData //只要继承ISaveData就可以了
{
...
}
C# 操作符优先级(从高到低)
初级操作符 () x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
一元操作符 + - | ~ ++x --x (T)x
乘除操作符 * / %
加减操作符 + -
位移操作符 << >>
关系操作符 < > <= >= is
等于操作符 ==
逻辑与 &
逻辑异或 ^
逻辑或 |
条件与 &&
条件或 ||
条件操作符 ?:
赋值操作符 = *= /= %= += -= <<= >>= &= ^= |=
所有的二元操作符除赋值符外都是左联合的,即从左到右计算。
typeof()运算符可以从一个类名得到一个System.Type对象,而从System.Object对象继承来的GetType ()方法
则可从一个类实例来得到一个System.Type对象。例:
Type t1 = typeof(Apple); //Apple是一个类名
Apple apple = new Apple(); //apple是Apple类的一个实例
Type t2 = apple.GetType(); //t1与t2是相同的
通过反射得到一个类的所有成员和方法:
Type t = typeof(Apple);
string className = t.ToString(); //得到类名
MethodInfo[] methods = t.GetMethods(); //得到所有方法
foreach (MethodInfo method in methods)
{
//用method.ToString()得到方法名
}
MemberInfo[] members = t.GetMembers(); //得到所有成员
foreach (MemberInfo member in members)
{
//用member.ToString()得到成员名
}
sizeof()操作符用来计算值类型变量在内存中占用的字节数(Bytes),并且它只能在unsafe(非安全)
代码中使用。例:
static unsafe public void ShowSizes()
{
int i, j;
j = sizeof(short);
j = sizeof(i);
}
尽可能使用复合赋值操作符,它比不用复合赋值操作符的效率高。
for语句的语法为:
for (initialization; Boolean-expression; step)
embedded-statement
在initialization和step部份还可以使用逗号操作符,例:
for (int i = '0', j = 1; i <= '\xFF'; i++, j++)
for (int i = 1, j = 1; i < 1000; i += j, j = i - j) //输出斐波那契数列
Console.Write("{0} ", i);
在switch语句中执行一个分支的代码后还想执行另一个分支的代码,可以用:
goto case 分支;
操作符重载是为了让程序更加自然,容易理解。想要为一个类重新定义一个操作符,使用以下语法:
public static 返回值 operator 操作符 (操作对象1[,操作对象2])
说明:
1)所有重载的操作符方法都必须定义为public和static
2)从技术上说返回值可以是任何类型,但通常是返回所定义方法使用的类型
3)操作对象的数目取决于重载是一元操作符还是二元操作符,一元操作符只要一个操作对象,二元操作符则需要二个。
4)不管重载是一元操作符还是二元操作符,第一个操作对象的类型都必须与返回值的类型一致;而对于二元操作符的第二个
操作对象的类型则可以是任何类型。
5)只有下列操作符可以被重载:
一元:+ - ! ~ ++ -- true false
二元:+ - * / % & | ^ << >> == != > < >= <=
赋值操作符(+=,-=,*-,/=,%=等等)无法被重载。
[]和()操作符也无法被重载。
6)操作符的优先级是无法改变的,运算优先级的规则是静态的。
例:假设一个Invoice发票类由多个InvoiceDetailLine类(成员只有一个Double类型的Amount金额属性)组成,
我们重载+操作符,使之可以将InvoiceDetailLine类的内容(注意不是金额合计)加在一起。
class Invoice
{
public ArrayList DetailLine;
public Invoice //类的析构函数
{
DetailLine = new ArrayList(); //ArrayList存放多个InvoiceDetailLine类的实例
}
public static Invoice operator+ (Invoice Invoice1, Invoice Invoice2) //参数与返回值的类型一致
{
//Invoice1与Invoice2的内容合并
Invoice ReturnInvoice = new Invoice();
foreach(InvoiceDetailLine detailLine in Invoice1.DetailLines)
ReturnInvoice.DetailLine.Add(detailLine);
foreach(InvoiceDetailLine detailLine in Invoice2.DetailLines)
ReturnInvoice.DetailLine.Add(detailLine);
return ReturnInvoice;
}
}
class InvoiceAddApp //调用示例
{
public static void main()
{
Invoice i1 = new Invoice();
for(int i = 0; i < 3; i++)
i1.DetailLine.Add(new InvoiceDetailLine(i + 1));
Invoice i2 = new Invoice();
for(int i = 0; i < 3; i++)
i2.DetailLine.Add(new InvoiceDetailLine(i + 1));
Invoice summaryInvoice = i1 + i2; //调用重载的操作符+方法
}
}
自定义类型转换可以编写代码实际二个不同的类、结构体之间的转换。
语法:public static implicite/explicite operator 输出类型 (输入类型)
说明:
1)转换方法必须是静态的。
2)implicite表示隐式转换,explicite表示显式转换。
3)输入类型和输出类型其中之一必须与包含转换的类或结构体类型。即转换必须与本类相关。
例:
struct Celisus
{
public float t;
public Celisus(float t)
{
this.t = t; //this.t是结构体的字段,t是参数
}
public static implicite operator Celisus(float t) //float=>Celisus
{
return new Celisus(t);
}
public static implicite operator float(Celisus c) //Celisus=>float
{
return ((c.t - 32) / 9) * 5;
}
}
代表的(delegate)目的与C++中的函数指针相同,代表不是在编译时被定义的,而是在运行时被定义的。
代表主要有二个用途:回调(Callback)和事件处理(event)
回调通常用于异步处理和自定义处理。例:
class DBManager
{
static DBConnection[] activeConnections;
//声明回调函数
public void delegate EnumConnectionCallback(DBConnection connection);
public static void EnumConnections(EnumConnectionCallback callback)
{
foreach (DBConnection connection in activeConnections)
{
callback(connection); //执行回调函数
}
}
}
//调用
class DelegateApp
{
public static void ActiveConncetionCallback(DBConnection connection) //处理函数
{
...
}
public void main()
{
//创建指向具体处理函数的代表实例(新建一个代表,让它指向具体的处理函数)
DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
DBManager.EnumConnections (myCallback);
}
}
//使用静态代表,上面的调用改为
class DelegateApp
{
//创建一个指向处理函数的静态代表
public static DBManager.EmnuConnectionCallback myCallback
= new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
public static void ActiveConncetionCallback(DBConnection connection)
{
...
}
public void main()
{
DBManager.EnumConnections(myCallback);
}
}
//在需要时才创建代表,上面的调用改为
class DelegateApp
{
//将创建代表放在属性的getter方法中
public static DBManager.EmnuConnectionCallback myCallback
{
get
{
retun new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
}
}
public static void ActiveConncetionCallback(DBConnection connection)
{
...
}
public void main()
{
DelegateApp app = new DelegateApp(); //创建应用程序
DBManager.EnumConnections (myCallback);
}
}
可以将多个代表整合成单个代表,例:
class CompositeDelegateApp
{
public static void LogEvent(Part part)
{
...
}
public static void EmailPurchasingMgr(Part part)
{
...
}
public static void Main()
{
//定义二个代表
InventoryManager.OutOfStockExceptionMethod LogEventCallback
= new InventoryManager.OutOfStockExceptionMethod(LogEvent);
InventoryManager.OutOfStockExceptionMethod EmailPurchasingMgrCallback
= new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
//整合为一个代表,注意后加的代表先执行(这里是先执行LogEventCallback)
InventoryManager.OutOfStockExceptionMethod onHandExceptionEventsCallback
= EmailPurchasingMgrCallback + LogEventCallback;
//调用代表
InventoryManager mgr = new InventoryManager();
mgr.ProcessInventory(onHandExceptionEventsCallback);
//InventoryManager类的ProcessInventory方法的原型为:
//public void ProcessInventory(OutOfStockExceptionMethod exception);
}
}
可以根据需要将多个代表自由地组合成单个代表,例:
class CompositeDelegateApp
{
//代表指向的处理函数(三个代表三个函数)
public static void LogEvent(Part part)
{
...
}
public static void EmailPurchasingMgr(Part part)
{
...
}
public static void EmailStoreMgr(Part part)
{
...
}
public static void Main()
{
//通过数组定义三个代表
InventoryManager.OutOfStockExceptionMethod[] exceptionMethods
= new InventoryManager.OutOfStockExceptionMethod[3];
exceptionMethods[0] = new InventoryManager.OutOfStockExceptionMethod (LogEvent);
exceptionMethods[1] = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
exceptionMethods[2] = new InventoryManager.OutOfStockExceptionMethod(EmailStoreMgr);
int location = 1;
//再定义一个代表(用于组合成单代表)
InventoryManager.OutOfStockExceptionMethod compositeDelegate;
//根据需要组合
if (location = 2)
{
compositeDelegate = exceptionMethods[0] + exceptionMethods[1];
}
else
{
compositeDelegate = exceptionMethods[0] + exceptionMethods[2];
}
//调用代表
InventoryManager mgr = new InventoryManager();
mgr.ProcessInventory(compositeDelegate);
}
}
C#的事件遵循"发布——预订"的设计模式。在这种模式中,一个类公布能够出现的所有事件,
然后任何的类都可以预订这些事件。一旦事件产生,运行环境就负责通知每个订户事件已经发生了。
当代表作为事件的处理结果时(或者说定义具有代表的事件),定义的代表必须指向二个参数的方法:
一个参数是引发事件的对象(发布者),另一个是事件信息对象(这个对象必须从EventArgs类中派生)。
例:
using System;
class InventoryChangeEventArgs: EventArgs //事件信息对象,从EventArgs类派生
{
... //假设定义二个public属性string Sku和int Change
}
class InventoryManager //事件的发布者
{
//声明代表
public delegate void InventoryChangeEventHander(object source, InventoryChangeEventArgs e);
//发布事件,event关键字可将一个代表指向多个处理函数
public event InventoryChangeEventHandler onInventoryChangeHander;
public void UpdateInventory(string sku, int change)
{
if (change == 0)
return;
InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change);
//触发事件
if (onInventoryChangeHandler != null) //如果有预订者就触发
onInventoryChangeHandler(this, e); //执行代表指向的处理函数
}
}
class InventoryWatcher //事件的预订者
{
public InventoryWatcher(InventoryManager mgr) //mgr参数用于联结发布者
{
this.inventoryManager = mgr;
//预订事件,用 += 调用多个处理函数
mgr.onInventroyChangeHandler += new InventoryManager.InventoryChangeEventHandler (onInventoryChange);
//事件处理函数
void onInventroyChange(object source, InventroyChangeEventArgs e)
{
...
}
InventoryManager inventoryManager;
}
}
class EventsApp //主程序
{
public static void Main()
{
InventoryManager inventoryManager = new InventoryManager();
InventoryWatcher inventoryWatcher = new InventoryWatcher(inventoryManager);
inventoryManager.UpdateInventory ("111 006 116", -2);
inventoryManager.UpdateInventory("111 006 116", 5);
}
}
Microsoft Windows NT和IBM OS/2等操作系统都支持占先型多任务。在占先型多任务执行中,处理器负责
给每个线程分配一定量的运行时间——一个时间片(timeslice)。处理器接着在不同的线程之间进行切换,
执行相应的处理。在单处理器的计算机上,并不能真正实现多个线程的同时运行,除非运行在多个处理器
的计算机上。操作系统调度的多线程只是根据分配给每个线程时间片进行切换执行,感觉上就像同时执行。
上下文切换(context switching)是线程运行的一部分,处理器使用一个硬件时间来判断一个指定线程的时间片
何时结束。当这个硬件计时器给出中断信号时,处理器把当前运行的线程所用的所有寄存器(registers)数据
存储到堆栈中。然后,处理器把堆栈里那些相同的寄存器信息存放到一种被称为"上下文结构"的数据结构中。
当处理器要切换回原来执行的线程时,它反向执行这个过程,利用与该线程相关的上下文结构,在寄存器里
重新恢复与这一线程相关的信息。这样的一个完整过程称为"上下文切换"。
多线程允许应用程序把任务分割为多个线程,它们彼此之间可以独立地工作,最大限度地利用了处理器时间。
using System;
using System.Threading;
class SimpleThreadApp
{
public static void WorkerThreadMethod() //线程的执行体
{
... //执行一些操作
}
public static void Main()
{
//创建一个线程代表指向线程的执行体,ThreadStart是创建新线程必须用到的代表
ThreadStart worker = new ThreadStart(WorkerThreadMethod);
Thread t = new Thread(worker); //用线程代表创建线程
t.Start(); //执行线程
}
}
可以通过两种方式来得到一个Thread对象:一种是通过创建一个新线程来得到,如上例;另一种在正在执行的线程调用
静态的Thread.CurrentThread方法。
静态方法Thread.Sleep(int ms)可以让当前线程(它自动调用Thread.CurrentThread)暂停指定毫秒的时间。
如果使用Thread.Sleep(0)那么当前线程将一直处于等待中,直到另一个线程调用这个线程的实例方法Thread.Interrupt方法,
等待才会结束。
使用Thread.Suspend方法也能挂起线程,Thread.Suspend 方法可以被当前线程或其他线程调用,而Thread.Sleep(0)
只能由当前线程在执行体中调用。当线程用Thread.Suspend挂起时,必须用Thread.Resume方法恢复。不论Thread.Suspend
方法调用了多少次,只要调用Thread.Resume方法一次就可以线程恢复执行。用Thread.Suspend方法并不会阻塞线程,
调用立即返回。而Thread.Sleep(0)则会阻塞线程。所以确切地说Thread.Sleep (0)暂停线程,而不是挂起线程。
使用Thread.Abort方法可以终止正在执行的线程。当Thread.Abort方法被调用时,线程不会立即终止执行。运行环境将会
等待,直到线程到达文档中所描述的"安全点"。如果要确保线程已经完全停止,可以使用Thread.Join方法。这是一个同步
调用,同步调用意味着直到线程完全停止,调用才会返回。
Thread.Priority属性用于设置的线程的优先级。其值是Thread.ThreadPriority枚举值,可以设为Highest, AboveNormal,
Normal, BelowNormal, Lowest。缺省值是Thread.ThreadPriority.Normal。
线程的同步是为了解决多个线程同时使用同一对象产生的一些问题。通过同步,可以指定代码的临界区(critical section),
一次只有一个线程可以进入临界区。
使用System.Monitor类(锁定与信号量)进行线程同步:
using System;
using System.Threading;
public void SaveData(string text) //线程执行函数或线程执行函数调用的对象的方法
{
... //执行其他一些不需要同步的处理
Monitor.Enter(this); //获取对象的Monitor锁
... //执行需要同步的处理
Monitor.Exit(this); //释放对象的Monitor锁
... //执行其他一些不需要同步的处理
}
说明:当执行Monitor.Enter方法时。这个方法会试图获取对象上的Monitor锁,如果另一个线程已经拥有了
这个锁,这个方法将会阻塞(block),直到这个锁被释放。
也可用C#的lock语句来获得和释放一个Monitor锁。上面同步写成:
public void SaveData(string text) //线程执行函数或线程执行函数调用的对象的方法
{
... //执行其他一些不需要同步的处理
lock(this) //获取对象的Monitor锁,代码块执行完成后释放Monitor锁
{
... //执行需要同步的处理
}
... //执行其他一些不需要同步的处理
}
也可以使用System.Threading名称空间的Mutex类(互斥类)进行线程同步。与Monitor锁一样,一次只有一个线程
能获得一个给定的互斥。但Mutex要慢得多,但它增加了灵活性。例:
using System;
using System.Threading;
class Database
{
Mutex mutex = new Mutex(false); //创建一个互斥,但不立即获得它
//注意:创建互斥在需要同步的方法之外,实际上它只要创建一个实例
public void SaveData(string text) //需要同步的方法
{
mutex.WaitOne(); //等待获得互斥
... //需要同步的处理
mntex.Close(); //释放互斥
}
}
Mutex类重载了三个构造函数:
Mutex() //创建并使创建类立即获得互斥
Mutex(bool initiallyOwned) //创建时可指定是否要立即获得互斥
Mutex(bool initiallyOwned, string muterName) //还可以指定互斥的名称
Mutex.WaitOne方法也重载了三次:
Mutex.WaitOne() //一直等待
Mutex.WaitOne(TimeSpan time, bool exitContext) //等待TimeSpan指定的时间
Mutex.WaitOne(int milliseconds, bool exitContext) //等待指定的毫秒
线程的用法:
1)并发操作:比如一个程序监视多个COM口,当每个COM接到信息时执行一段处理时。
2)复杂长时间操作:一个长时间的复杂操作可能会使界面停滞,停止用户响应,如果还允许用户停止它,
或者显示进度条、显示操作执行进程信息时。
反射(Reflection)就是能够在运行时查找类型信息,这是因为.NET编译的可执行(PE)文件中包括MSIL和元数据(metadata)。
反射的中心是类System.Type。System.Type是一个抽象类,代表公用类型系统(Common Type System, CTS)中的一种类型。
using System;
using System.Reflection; //反射命名空间,必须引用
public static void Main(string[] args)
{
int i = 6;
Type t = i.GetType(); //根据实例得到类型
t = Type.GetType("System.Int32"); //根据类型的字符名称得到类型
}
通过Assembly类可以得到已经编译.NET Framework程序的中所有类型,例:
using System;
using System.Diagnostics; //为了使用Process类
using System.Reflection; //为了使用Assembly类
class GetTypesApp
{
protected static string GetAssemblyName(string[] args)
{
string assemblyName;
if (0 == args.Length) //如果参数为空,取当前进程的名称
{
Process p = Process.GetCurrentProcess();
assemblyName = p.ProcessName + ".exe";
}
else
assemblyName = args[0]; //取第一个参数,即当前运行程序名
return assemblyName;
}
public static void Main(string[] args)
{
string assemblyName = GetAssemblyName(args);
Assembly a = Assembly.LoadFrom (assemblyName); //调用编译程序集
Type[] types = a.GetTypes(); //得到多个类型
foreach (Type t in types) //遍历类型数组
{
... //取得t.FullName,t.BaseType.FullName等类型信息
}
}
}
一个应用程序可以包括多个代码模块。若要将一个cs文件编译一个模块,只要执行下面的命令:
csc /target:module 要编译的模块.cs //csc是C Sharp Compiler(C#编译器)
然后在应用程序中using编译的模块.cs中的NameSpace即可应用了。
要反射应用程序中所有代码模块(Module),只要:
Assembly a = Assembly.LoadFrom(assemblyName); //应用程序的物理文件名
Module[] modules = a.GetModules ();
foreach(Module m in modules)
{
... //显示m.Name等
}
后期绑定(latebinding),例:
string[] fileNames = Directory.GetFiles(Environment.CurrentDirectory, "*.dll");
foreach (string fileName in fileNames)
{
Assembly a = Assembly.LoadFrom(fileName);
Type[] types = a.GetTypes();
foreach(Type t in types)
{
if (t.IsSubclassOf(typeof(CommProtocol))) //判断是否有CommProtocol的派生类
{
object o = Activator.CreateInstance(t); //生成实例
MethodInfo mi = t.GetMethod("DisplayName");
mi.Invoke(o, null); //调用方法
}
}
}
//带参数的例子
namespace Programming_CSharp
{
using System;
using System.Reflection;
public class Tester
{
public static void Main( )
{
Type t = Type.GetType("System.Math");
Object o = Activator.CreateInstance(t);
// 定义参数类型
Type[] paramTypes = new Type[1];
paramTypes[0]= Type.GetType("System.Double");
MethodInfo CosineInfo = t.GetMethod("Cos", paramTypes);
//设置参数数据
Object[] parameters = new Object[1];
parameters[0] = 45;
//执行方法
Object returnVal = CosineInfo.Invoke(o, parameters);
Console.WriteLine("The cosine of a 45 degree angle {0}", returnVal);
}
}
}
动态生成代码和动态调用的完整例子:
//动态生成代码的部分
using System;
using System.Reflection;
using System.Reflection.Emit; //动态生成代码必须引用
namespace ILGenServer
{
public class CodeGenerator
{
public CodeGenerator()
{
currentDomain = AppDomain.CurrentDomain; //得到当前域
assemblyName = new AssemblyName(); //从域创建一个程序集
assemblyName.Name = "TempAssembly";
//得到一个动态编译生成器, AssemblyBuilerAccess.Run表示只在内存中运行,不能保存
assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilerAccess.Run);
//从编译生成器得到一个模块生成器
moduleBuilder = assemblyBuilder.DefineDynamicModule ("TempModule");
//模块生成器得到类生成器
typeBuilder = moduleBuilder.DefineType("TempClass", TypeAttributes.Public);
//为类添加一个方法
methodBuilder = typeBuilder.DefineMethod("HelloWord", MethodAttributes.Public, null, null);
//为方法写入代码,生成代码必须使用到IL生成器
msil = methodBuilder.GetILGenerator();
msil.EmitWriteLine("Hello World");
msil.Emit(OpCodes.Ret);
//最后还需要编译(build)一下类
t = typeBuilder.CreateType();
}
AppDomain currentDomain;
AssemblyName assemblyName;
AssemblyBuilder assemblyBuilder;
ModuleBuilder moduleBuilder;
TypeBuilder typeBuilder;
MethodBuilder methodBuilder;
ILGenerator msil;
object o;
Type t;
public Type T
{
get
{
return this.t;
}
}
}
}
//动态调用的部分
using System;
using System.Reflection;
using ILGenServer; //引用动态生成代码的类
public class ILGenClientApp
{
public static void Main(
{
CodeGenerator gen = new CodeGenerator(); //创建动态生成类
Type t = gen.T;
if (null != t)
{
object o = Activator.CreateInstance(t);
MethodInfo helloWorld = t.GetMethod("HelloWorld"); //为调用方法创建一个MethodInfo
if (null != helloWorld)
{
helloWorld.Invoke(o, null); //调用方法
}
}
}
}
调用DLL
using System;
using System.Runtime.InteropServices; //为了使用DLLImport特性
class PInvokeApp
{
[DllImport("user32.dll ", CharSet=CharSet.Ansi)] //CharSet.Ansi指定Ansi版本的函数(MessageBoxA),CharSet.Unicode指定Unicode版本的函数(MessageBoxW)
static extern int MessageBox(int hWnd, string msg, string caption, int type); //声明DLL中的函数
//[DllImport(" user32.dll", EntryPoint="MessageBoxA")] //用这种方法使用不同的函数名
//static extern int MsgBox(int hWnd, string msg, string caption, int type);
//[DllImport("user32.dll", CharSet=CharSet.Unicode )] //调用Unicode版的DLL函数
//static extern int MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]string msg,
// [MarshalAs(UnmanagedType.LPWStr)]string caption, int type); //将LPWStr翻译为string型,缺省情况系统只将LPStr翻译成string
public static void Main()
{
MessageBox(0, "Hello, World!", "CaptionString", 0); //调用DLL中的函数
}
}
例2,使用回调:
class CallbackApp
{
[DllImport("user32.dll")]
static extern int GetWindowText(int hWnd, StringBuilder text, int count);
delegate bool CallbackDef(int hWnd, int lParam);
[DllImport("user32.dll")]
static extern int EnumWindows(CallbackDef callback, int lParam);
static bool PrintWindow(int hWnd, int lParam)
{
StringBuilder text = new StringBuilder(255);
GetWindowText(hWnd, text, 255);
Console.WriteLine("Window Caption: {0}", text);
return true;
}
static void Main()
{
CallbackDef callback = new CallbackDef(PrintWindow);
EnumWindows(callback, 0);
}
}
关键字unsafe指定标记块在非控环境中运行。该关键字可以用于所有的方法,包括构造函数和属性,
甚至还有方法中的代码块。关键字fixed负责受控对象的固定(pinning)。Pinning是一种动作,向
垃圾收集器(Garbage Collector, GC)指定一些不能被移动的对象。为了不在内存中产生碎片,.NET
运行环境把对象四处移动,以便于最有效地利用内存。使用fixed后指定对象将不会被移动,所以就
可以用指针来访问它。
C#中只能得到值类型、数组和字符串的指针。在数组的情况下,第一个元素必须是值类型,因为C#
实际上是返回一个指向数组第一个元素的指针,而不是返回数组自身。
& 取一个变量的内存地址(即指向该变量的指针)
* 取指针所指变量的值
-> 取成员
例:
using System;
class UnsafeApp
{
public static unsafe void Getvalues(int* x, int* y)
{
*x = 6;
*y = 42;
}
public static unsafe void Main()
{
int a = 1;
int b = 2;
Getvalues(&a, &b);
}
}
fixed语法为:fixed(type* ptr = expression) statements
其中type也可以为非控类型,也可是void;expression是任何产生一个type指针的表达式;
statements是应用的代码块。例:
fixed (int* f = &foo.x) //foo是Foo类的一个实例,x是Foo类的一个int属性
{
SetFoovalue(f); //SetFoovalue方法的定义为unsafe static void SetFoovalue(int* x)
}
传统的COM组件可以通过互操作层(COM Interop)与.NET运行环境交互。互操作层处理在托管运行环境和非托管区域
中的COM组件操作之间传递所有的消息。
要使COM组件能在.NET环境中使用,必须为COM组件生成元数据。.NET运行环境用元数据层业判断类型信息。在运行时刻
使用类型信息,以便生成RCW(Runtime Callable Wrapper,运行时可调用包装)。当.NET应用程序与COM对象交互时,
RCW处理对COM对象的装载和调用。RCW还完成许多其他的工作,如管理对象标识、对象生存周期以及接口缓冲区。
对象生存周期管理十分关键,因为.NET GC把对象到处移动,并且当对象不再使用时,自动处理这些对象。RCW服务告诉
.NET,应用程序正与托管.NET组件交互,同时又使非托管COM组件"觉得"COM对象是被传统的COM客户端调用的。
为了为COM组件生成元数据包装,必须使用tlbimp.exe(TypeLib Importer)工具:
tlbimp some_COM.tlb /out:som_COM.dll