第一篇:C#学习心得
C#速成
一、绪论
C#是这样的一种语言,具有C++的特点,象Java一样的编程风格, 并且象Basic一样的快速开发模型。如果你已经知道了C++,本文会在不到一个小时的时间内让你迅速掌握C#的语法。熟悉Java的括会更好,因为Java的程序结构、打包(Packages)和垃圾收集的概念有助于你更快的了解C#。因此在讨论C#的构造时,我会假定你了解C++。
本文会讨论C#语言的构造与特点,同时会采取简洁的和你能理解的方式使用些代码示例,我们会尽量让你能稍微看看这些代码就能理解这些概念。
注意:本文不是为C#高手(C# gurus)所写.这是针对在C#学习上还是初学者的文章。下面是将要讨论的C#问题的目录: 程序结构 命名空间 数据类型 变量
运算符和表达式 枚举
语句(Statements)
类(Classes)和结构(Structs)修饰符(Modifiers)属性(Properties)接口(Interfaces)方法参数(Function Parameters)数组(Arrays)索引器(Indexers)装箱及拆箱操作 委托(Delegates)继承和多态
下面的内容将不会在被讨论之列:
C++与C#谁更通用
诸如垃圾回收、线程以及文件处理等概念 数据的类型转换 异常处理.NET库
二、程序结构
这一点象C++,C#是一种对大小写字母敏感的语言,分号“;”是语句间的分隔符。与C++不同的是,C#当中声明代码文件(头文件)与实现代码文件(cpp文件)不是独立存在的,所有代码(类声明和类实现)都位于一个扩展名为cs的文件内。
让我们瞧瞧C#当中的 Hello world 程序是怎样的。using System;namespace MyNameSpace { class HelloWorld { static void Main(string[] args){ Console.WriteLine(“Hello World”);} } }
在C#当中的每样东西都被封装到一个类中,C#的类又被封装到一个命名空间当中(就象一个文件夹中的文件)。类似于 C++,main方法是你的程序的入口点。C++的main函数调用名称是“main”,而C#的main函数是以大写字母M为起点的名称是“Main”。
没有必要把分号分隔符放在类语句块或者结构定义语句块后。这在C++当中被要求,但在C#当中却不是。
三、命名空间
每一个类都被包装进一个命名空间。命名空间的概念与C++的完全相同,但在C#当中使用命名空间的频率较C++还高。你可以使用点限定符(dot qulifier)访问一个类。在上面的hello world程序当中MyNameSpace就是一个命名空间。
现在思考这样的一个问题,你想从某些别的类的命名空间当中来访问HelloWorld这个类该如何操作。
这有一个例子:
using System;namespace AnotherNameSpace { class AnotherClass { public void Func(){ Console.WriteLine(“Hello World”);} } }
现在,从你的HelloWorld类里你能象这样去访问上面的这个AnotherNameSpace的命名空间: using System;using AnotherNameSpace;// you will add this using statement namespace MyNameSpace { class HelloWorld { static void Main(string[] args){ AnotherClass obj = new AnotherClass();obj.Func();} } }
在.NET库当中,System是位于顶层的命名空间,别的命名空间都存在这个命名空间之下。默认状态下,存在一个全局的命名空间,因此一个在命名空间外定义的类将直接在这个全局命名空间之下;因此,你能在没有任何点限定符的情况下访问这个类。
四、变量
除以下区别外,C#当中的变量几乎与C++同:
与C++不同,C#变量被访问之前必须被初始化;否则编译时会报错。因此,访问一个未初始化变量是不可能的事。
C#中你不会访问到一个不确定的指针。(译者注:严格说起来C#已经把指针概念异化,限制更严格。所以有些资料上会说C#取消了指针概念)一个超出数组边界的表达式是不可访问的。
C#中没有全局(整个Application)的变量或全局函数,全局方式的操作是通过静态函数和静态变量来实现的。
五、数据类型
所有C#数据类型都派生自基类Object。这里有两类数据类型: 基本型/内置型 用户自定义型 下面一个C#内置类型列表:
类型 字节数 解释 byte 1 无符号字节型 sbyte 1 有符号字节型 short 2 有符号短字节型 ushort 2 无符号短字节型 int 4 有符号整型 uint 4 无符号整型 long 8 有符号长整型 ulong 8 无符号长整型 float 4 浮点数 double 8 双精度数 decimal 8 固定精度数 string unicode字串型 char unicode字符型 bool 真假布尔型
注意:C#当中的类型范围与C++有所不同;例如,C++的long型是4个字节,而在C#当中是8个字节。同样地,bool型和string型都不同于C++。bool型只接受true和false两种值。不接受任何整数类型。
用户定义类型包括: 类类型(class)结构类型(struct)接口类型(interface)
数据类型的内存分配形式的不同又把它们分成了两种类型: 值类型(Value Types)
引用类型(Reference Types)
值类型:
值类型数据在栈中分配。他们包括:所有基本或内置类型(不包括string类型)、结构类型、枚举类型(enum type)
引用类型:
引用类型在堆中分配,当它们不再被使用时将被垃圾收集。它们使用new运算符来创建,对这些类型而言,不存在C++当中的delete操作符,根本不同于C++会显式使用delete这个运算符去释放创建的这个类型。C#中,通过垃圾收集器,这些类型会自动被收集处理。引用类型包括:类类型、接口类型、象数组这样的集合类型类型、字串类型、枚举类型 枚举类型与C++当中的概念非常相似。它们都通过一个enum关键字来定义。示例:
enum Weekdays { Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday }
类类型与结构类型的比较
除了在内存分配形式上外,类与结构的概念完全与C++相同。类的对象被分配在堆中,并且通过new来创建,结构也是被new创建但却被分配在栈当中。C#当中,结构型适于快速访问和拥有少量成员的数据类型。如果涉及量较多,你应该创建一个类来实现他。
(译者注:这与堆和栈内存分配结构的特点有关。简而言之,栈是一种顺序分配的内存;堆是不一定是连续的内存空间。具体内容需要大家参阅相关资料)示例:
struct Date { int day;int month;int year;}
class Date { int day;int month;int year;string weekday;string monthName;
public int GetDay(){ return day;}
public int GetMonth(){ return month;}
public int GetYear(){ return year;}
public void SetDay(int Day){ day = Day;}
public void SetMonth(int Month){ month = Month;}
public void SetYear(int Year){ year = Year;}
public bool IsLeapYear(){ return(year/4 == 0);}
public void SetDate(int day, int month, int year){
}...}
六、属性
如果你熟悉C++面象对象的方式,你就一定有一个属性的概念。在上面示例当中,以C++的观点来看,Data类的属性就是day、month和year。用C#方式,你可以把它们写成Get和Set方法。C#提供了一个更方便、简单、直接的方式来访问属性。
因此上面的类可以被写成:
using System;class Date { int day;public int Day{ get { return day;}
set { day = value;} }
int month;
public int Month{
get { return month;}
set { month = value;} }
int year;
public int Year{
get { return year;}
set { year = value;} }
public bool IsLeapYear(int year){ return year%4== 0 ? true: false;}
public void SetDate(int day, int month, int year){ this.day = day;this.month = month;this.year = year;} }
你可在这里得到并设置这些属性:
class User {
public static void Main(){ Date date = new Date();date.Day = 27;date.Month = 6;date.Year = 2003;Console.WriteLine(“Date: {0}/{1}/{2}”, date.Day, date.Month, date.Year);} }
七、修饰符
你必须已经知道public、private、protected这些常在C++当中使用的修饰符。这里我会讨论一些C#引入的新的修饰符。readonly(只读)
readonly修饰符仅在类的数据成员中使用。正如这名字所提示的,readonly 数据成员仅能只读,它们只能在构造函数或是直接初始化操作下赋值一次。readonly与const数据成员不同,const 要求你在声明中初始化,这是直接进行的。看下面的示例代码: class MyClass {
const int constInt = 100;//直接初始化
readonly int myInt = 5;//直接初始化
readonly int myInt2;//译者注:仅做声明,未做初始化
public MyClass(){ myInt2 = 8;//间接的 }
public Func(){ myInt = 7;//非法操作(译者注:不得赋值两次)Console.WriteLine(myInt2.ToString());} }
sealed(密封)
密封类不允许任何类继承,它没有派生类。因此,你可以对你不想被继承的类使用sealed关键字。
sealed class CanNotbeTheParent { int a = 5;}
unsafe(不安全)
你可使用unsafe修饰符来定义一个不安全的上下文。在不安全的上下文里,你能写些如C++指针这样的不安全的代码。看下面的示例代码:
public unsafe MyFunction(int * pInt, double* pDouble){ int* pAnotherInt = new int;*pAnotherInt = 10;pInt = pAnotherInt;...*pDouble = 8.9;}
八、interface(接口)
如果你有COM方面的概念,你会立亥明白我要谈论的内容。一个接口就是一个抽象的基类,这个基类仅仅包含功能描述,而这些功能的实现则由子类来完成。C#中你要用interface关键字来定义象接口这样的类。.NET就是基于这样的接口上的。C#中你不支持C++所允许的类多继承(译者注:即一个派生类可以从两个或两个以上的父类中派生)。但是多继承方式可以通过接口获得。也就是说你的一个子类可以从多个接口中派生实现。interface myDrawing { int originx { get;set;} int originy { get;set;} void Draw(object shape);} class Shape: myDrawing { int OriX;int OriY;public int originx { get{ return OriX;} set{ OriX = value;} } public int originy { get{ return OriY;} set{ OriY = value;} } public void Draw(object shape){...// do something }
// class's own method public void MoveShape(int newX, int newY){.....} }
九、Arrays(数组)
C#中的数组比C++的表现更好。数组被分配在堆中,因此是引用类型。你不可能访问超出一个数组边界的元素。因此,C#会防止这样类型的bug。一些辅助方式可以循环依次访问数组元素的功能也被提供了,foreach就是这样的一个语句。与C++相比,C#在数组语法上的特点如下: 方括号被置于数据类型之后而不是在变量名之后。创建数组元素要使用new操作符。
C#支持一维、多维以及交错数组(数组中的数组)。示例:
int[] array = new int[10];// 整型一维数组 for(int i = 0;i < array.Length;i++){ array[i] = i;} int[,] array2 = new int[5,10];// 整型二维数组 array2[1,2] = 5;int[,] array3 = new int[5,10,5];// 整型的三维数组 array3[0,2,4] = 9;int[][] arrayOfarray = = new int[2];// 整型交错数组(数组中的数组)arrayOfarray[0] = new int[4];arrayOfarray[0] = new int[] {1,2,15};
十、索引器
索引器被用于写一个访问集合元素的方法,集合使用“[]”这样的直接方式,类似于数组。你所要做的就是列出访问实例或元素的索引清单。类的属性带的是输入参数,而索引器带的是元素的索引表,除此而外,他们二者的语法相同。示例: 注意:CollectionBase是一个制作集合的库类。List是一个protected型的CollectionBase成员,储存着集合清单列表。class Shapes: CollectionBase { public void add(Shape shp){ List.Add(shp);} //indexer public Shape this[int index] { get { return(Shape)List[index];} set { List[index] = value;} } }
十一、装箱和拆箱操作(Boxing/Unboxing)
C#的装箱思想是全新的。上面提到过所有的数据类型,不论内置或用户自定义,全都从命名空间System的一个基类object派生出来。因此把基本的或者原始类型转换成object类型被称做装箱,反之,这种方式的逆操作被称为拆箱。示例: class Test { static void Main(){ int myInt = 12;object obj = myInt;// 装箱 int myInt2 =(int)obj;// 拆箱 } }
示例展示了装箱和拆箱操作。一个整型值转换成object类型,然后又转换回整型。当一个值类型的变量需要转换成引用类型时,一个object的箱子会被分配容纳这个值的空间,这个值会被复制进这个箱子。拆箱与此相反,一个object箱子中的数据被转换成它的原始值类型时,这个值将被从箱中复制到适当的存储位置。
十二、方法参数
C#中有三种类型的参数:
值参数/输入型参数
引用型参数/输入输出型参数 Out参数
如果你有COM接口和它的参数类型的概念,你会很容易理解C#参数类型。值参数/输入型参数
值概念与C++相同。所要传递的值会被复制到一个位置上并被传递给函数。示例: SetDay(5);void SetDay(int day){....}
引用型参数/输入输出参数
C#中的引用参数既不是C++中的指针也不是引用操作符(&)来传递的。C#的引用型参数减少了出错的可能。引用型参数也被称作输入输出参数,因为你传递了一个引用地址,因此你可以从函数中传递一个输入值并且可以获得一个输出值。
你不能把一个未经初始化的引用型参数传递给函数。C#用ref这个关键字来声明引用型参数。当你传递一个变量给函数要求的引用参数时必须使用一个ref关键字说明。示例:
int a= 5;FunctionA(ref a);// 要用ref声明变量,否则你会得到 // 一个编译错误
Console.WriteLine(a);// 指向地址的值为20 void FunctionA(ref int Val){ int x= Val;Val = x* 4;}
Out参数
Out型参数仅仅从函数当中返回一个值。不要求有输入值。C#用关键字out来描声明这个参数 示例:
int Val;GetNodeValue(Val);bool GetNodeValue(out int Val){ Val = value;return true;}
可变数量的参数和数组
数组在C#当中是通过关键字params来描述传递的。作为数组类型的变量,你能传递任意数量的元素。从下面示例中你可以理解的更好。示例:
void Func(params int[] array){ Console.WriteLine(“number of elements {0}”,array.Length);} Func();// prints 0 Func(5);// prints 1 Func(7,9);// prints 2 Func(new int[] {3,8,10});// prints 3 int[] array = new int[8] {1,3,4,5,5,6,7,5};Func(array);// prints 8
十三、运算符和表达式
运算符和表达式概念与C++完全相同。但是一些新的有用的运算符被填加了进来。我将在这里讨论其中的某些部分。
is 运算符
is 运算符被用于检查操作数的类型是否相同或者是否可以转换。is 运算符在多态环境下特别有用。它有两个操作数,运算结果是一个布尔型。看这个示例: void function(object param){ if(param is ClassA)//do something else if(param is MyStruct)//do something } } as 运算符
as 运算符检查操作数的类型是否可被转换或者是否相等(这些 as通过 is 运算符来完成。如果结果是可转换的,则结果将被转换或者被装箱,成object(关于as运算符进行装箱成目标类型的操作请看前面的装箱/拆箱操作)。如果不可转换或者装箱,则返回值是null。瞧一瞧下面的例子我们会更好地理解这个概念。Shape shp = new Shape();Vehicle veh = shp as Vehicle;// 结果是null, 类型不可转换 Circle cir = new Circle();Shape shp = cir;Circle cir2 = shp as Circle;//会被转换 object[] objects = new object[2];objects[0] = “Aisha”;object[1] = new Shape();string str;for(int i=0;i&< objects.Length;i++){ str = objects[i] as string;if(str == null)Console.WriteLine(“can not be converted”);else Console.WriteLine(“{0}”,str);} 输出: Aisha can not be converted
十四、语句
除了对某些新增语句和对某些语句的修改以外,C#语句与C++非常相象。下面是新增的语句: foreach 用于循环依次访问集合元素,比如象数组等。示例: foreach(string s in array)Console.WriteLine(s);lock 用于锁住代码块,使线程在临界争区内,别的线程无法进入锁定的临界区。
checked/unchecked 用于数值运算中的溢出检测。示例:
int x = Int32.MaxValue;x++;// 溢出检测 { x++;// 异常
}
unchecked { x++;// 溢出} }
下面的语句在C#当中已经被修改: Switch 执行一个case语句后,程序流程不允许跳到下一个相邻case语句。这在C++当中是被允许的。示例:
int var = 100;switch(var){ case 100: Console.WriteLine(“
case 200: Console.WriteLine(“
C++编译后的输出:
C#下,编译时会报错:
error CS0163: Control cannot fall through from one case label('case 100:')to another 但是你仍然能做C++类似的事 switch(var){ case 100: case 200: Console.WriteLine(“100 or 200
你也可以常数变量作为case 的值: 示例:
const string WeekEnd = “Sunday”;const string WeekDay1 = “Monday”;....string WeekDay = Console.ReadLine();switch(WeekDay){ case WeekEnd: Console.WriteLine(“It's weekend!”);break;case WeekDay1: Console.WriteLine(“It's Monday”);break;}
十五、委托
委托让我们把一个函数引用存储在一个变量里。C++当中,这类似于使用typedef定义的函数指针,我们通常用存储一个函数指针。
声明委托使用的关键字是 delegate。瞧瞧这个示例,你会理解什么是委托: 示例:
delegate int Operation(int val1, int val2);public int Add(int val1, int val2){ return val1 + val2;}
public int Subtract(int val1, int val2){ return val1-val2;}
public void Perform(){ Operation Oper;Console.WriteLine(“Enter + or-”);string optor = Console.ReadLine();Console.WriteLine(“Enter 2 operands”);string opnd1 = Console.ReadLine();string opnd2 = Console.ReadLine();int val1 = Convert.ToInt32(opnd1);int val2 = Convert.ToInt32(opnd2);if(optor == “+”)Oper = new Operation(Add);Else Oper = new Operation(Subtract);Console.WriteLine(“ Result = {0}”, Oper(val1, val2));}
十六、继承和多态
C#仅允许单继承,多继承要通过接口来实现。示例:
class Parent { } class Child : Parent { }
十七、虚拟方法
除了在子类中实现虚拟方法采用override关键字外,虚拟方法实现多态的概念C#与C++相同。父类使用相同的virtual关键字。从重载虚拟方法的每个类都要使用override关键字。class Shape { public virtual void Draw(){ Console.WriteLine(“Shape.Draw”);} }
class Rectangle : Shape { public override void Draw(){ Console.WriteLine(“Rectangle.Draw”);} }
class Square : Rectangle { public override void Draw(){ Console.WriteLine(“Square.Draw”);} }
class MainClass { static void Main(string[] args){ Shape[] shp = new Shape[3];Rectangle rect = new Rectangle();shp[0] = new Shape();shp[1] = rect;shp[2] = new Square();shp[0].Draw();shp[1].Draw();shp[2].Draw();} }
输出t: Shape.Draw Rectangle.Draw Square.Draw
十八、使用“new”来隐藏父方法
你可以定义一个子类成一个新方法版本,隐藏基类当中的那个版本。使用new关键字就可以定义一个新版本。思考下面的示例,它是上面示例的修改后的版本。注意当我用Rectangle类中的new关键字代替override关键字时示例的输出情况。
class Shape { public virtual void Draw(){ Console.WriteLine(“Shape.Draw”);} }
class Rectangle : Shape { public new void Draw(){ Console.WriteLine(“Rectangle.Draw”);} } class Square : Rectangle { //没在这里让你重载 public new void Draw(){ Console.WriteLine(“Square.Draw”);} } class MainClass { static void Main(string[] args){ Console.WriteLine(“Using Polymorphism:”);Shape[] shp = new Shape[3];Rectangle rect = new Rectangle();shp[0] = new Shape();shp[1] = rect;shp[2] = new Square();shp[0].Draw();shp[1].Draw();shp[2].Draw();Console.WriteLine(“Using without Polymorphism:”);rect.Draw();Square sqr = new Square();sqr.Draw();} } 输出: Using Polymorphism Shape.Draw Shape.Draw Shape.Draw Using without Polymorphism: Rectangle.Draw Square.Draw 这里的多态性不会把Rectangle类的Draw方法当做Shape的Draw方法多态性的一种表现。相反,它会认为这是一种不同的方法。因此,为了避免父类与子类间的命名冲突,我们使用了new修饰符。注意:你不能使用同一类下面一种方法的两个版本,即一个是用new修饰符的版本,另一个是用override或virtual修饰符的版本。正象上面示例所说明的,我不能再在拥有virtual或override方法的Rectangle类中添加另一个命名为Draw的方法。同样地,在Square类中,我也不能重载Square类的虚拟的Draw方法。
十九、调用基类成员
如果子类与基类有同名的数据成员,为避免命名冲突,访问基类数据成员和函要使用一个关键字base。在下面的示例中我们来看看如何调用基类的构造函数以及如何使用数据成员。public Child(int val):base(val){ myVar = 5;base.myVar;}
或者
public Child(int val){ base(val);myVar = 5;base.myVar;}
文学の音声図書館[转贴]
みなさん: こんにちは。私がつくった「文学の音声図書館」をご覧下さい。みなさまの聴力に役立つかもしれません。どうぞ。
http://作家辞典http://horagai.com/歌舞伎文庫http://http://日语教材 http://seki.nease.net/yufa.htm 语法网站======================================外来语从此不再可怕http://[align=right][color=#000066][此贴子已经被作者于2005-1-27 20:18:53编辑过][/color][/align]
第二篇:C#学习心得
集合声明:类B可以换成任意object对象
1、CollectionBase
类A继承CollectionBase类,通过CollectionBase的成员List实现类A的Add(类
B)、Remove(类B)和RemoveAt(类B)方法:
publicvoidAdd(类B newB)
{List.Add(newB);}
publicvoidRemove(类B newB)
{List.Remove(newB);}
publicvoidRemoveAt(int index)
{List.RemoveAt(index);}
在类A中建立索引可以按类似数组的方法访问。
public 类B this[int index]
{get{return(类B)List[index];}
set{List[index]=value;}
}
利用CollectionBase的成员InnerList(ArrayList对象)实现类A的Contains()方法:
publicboolContains(类B newB)
{
returnInnerList.Contains(newB);
}
注意:InnerList是ArrayList类实例,其Contains方法通过调用Object.Equals确定相等性,Equals默认实现仅支持引用相等。对于引用类型,相等定义为对象相等,即这些引用是否引用同一对象。对于值类型,相等定义为按位相等。
可以在类B中重写Object.Equals方法和GetHashCode()方法。publicoverrideboolEquals(objectobj)
{//Check for null and compare run-time types.if(obj == null || GetType()!= obj.GetType())returnfalse;
B b =(B)obj;
return(比较逻辑);
}
publicoverrideintGetHashCode(){„„}
2、DictionaryBase
类A继承DictionaryBase类,通过DictionaryBase的成员
Dictionary(IDictionary类型的接口),实现类A的 Add(object key,类B)和Remove(object key,类B)方法:
publicvoidAdd(object key,类B newB)
{Dictionary.Add(key,newB);}
publicvoidRemove(object key,类B newB)
{Dictionary.Remove(key,newB);}
在类A中建立索引可以按类似数组的方法访问。
public 类B this[object index]
{get{return(类B)Dictionary[index];}
set{Dictionary[index]=value;}
}
利用DictionaryBase的接口成员Dictionary实现类A的Contains()方法: publicboolContains(object key)
{
returnDictionary.Contains(key);
}
3、迭代器
对于继承CollectionBase类的A,使用
foreach(BsourceBin类A对象){}
对于继承DictionaryBase类的A,使用
foreach(DictionaryEntrysourceBin类A对象){source.Value.}
对于类迭代,使用方法GetEnumerator(),返回类型是IEnumerator;类成员迭代使用IEnumerable(),返回类型是IEnumerable;
例如继承DictionaryBase类的A的迭代器,public new IEnumeratorGetEnumerator()
{foreach(object b in Dictionary.Values)
yield return(B)b;
}
以后使用foreach循环时,可按照类似继承CollectionBase类的的方式使用。
4、浅度复制与深度复制
浅度复制:简单地按照成员复制对象可以通过派生于System.Object的MemberwiseClone()方法来完成,这是一个受保护的方法,但是很容易在对象上定义一个调用该方法的公共方法例如GetCopy()。这个方法的复制功能成为浅复制。浅拷贝是对引用类型拷贝地址,对值类型直接进行拷贝,但是string类例外,因为string是readonly的,当改变string类型的数据值时,将重新分配了内存地址。数组、类也是浅度复制,而结构体、数值型、枚举是深度复制。
深度复制:需要深度复制的类A添加ICloneable接口,实现该接口的Clone()方法。
public object Clone()
{A newA=new A();
object []arr=new object[维度];//object 可以是数值类型,string //不能使用newA.arr=arr;因为通过数组名赋值引用同一地址,是浅度复制 arr.CopyTo(newA.arr,0);
returnnewA;}
假设类A中有成员对象类B实例myB,则在类B定义中也要实现ICloneable的Clone()方法,class B:ICloneable
{
public object Clone(){„„}
}
然后在类A的Clone方法中,newA.myB=myB.Clone();
比较
1、is运算符
检查对象是否是给定类型或者是否可以转换为给定类型,是则返回true。
如果type是类类型,operand也是该类型,或继承该类型、封箱到该类型,为true 如果type是接口类型,operand也是该类型,或实现该接口的类型,为true 如果type是值类型,operand也是该类型,或拆箱到该类型,为true2、运算符重载
public static 返回类型 operator 需重载的运算符(参数„„){}
注意不能重载赋值运算符,&&和||运算符,但可重载&和|;有些运算符需成对重载,如“<”和“>”
3、IComparable接口
类A实现IComparable接口的方法intCompareTo(objectobj)后,利用成员为类A的实例的ArrayList或Array类可以调用Sort()方法,按CompareTo(objectobj)的方法排序。
4、IComparer接口
类A实现IComparer接口的方法intCompare(objectx, objecty)后,利用ArrayList或Array类可以调用Sort(IA)方法(IComparer IA=new A()),按
Compare(,)方法排序。注意ArrayList或Array类的实例不一定是类A。也可以在类A中定义一个公用动态接口成员IComparer ID,这样可以直接调用Sort(ID)。另外,在Compare方法中可以调用Comparer.Default.Compare(,)方法,实现特定的关键字排序。Default是Compare类的动态实例。
转换
1、隐式和显示转换
在没有继承关系,没有共享接口的类型之间转换时,必须定义类型之间的隐式和显示转换。public classA
{„„
//定义A到B的隐式转换
public staticimplicit operatorzhuanB(Aa){„„ return }
}
public classB
{„„
//定义B到A的显式转换
public staticexplicit operatorzhuanA(Bb){„„return }
}
2、as运算符
把类型转换为给定类型。
operand类型是type类型,或可以隐式转换为type类型,或封箱到type类型 如果不能转换,则表达式的结果是null
异常处理
Exception:所有异常对象的基类。
SystemException:运行时产生的所有错误的基类。
IndexOutOfRangeException:当一个数组的下标超出范围时运行时引发。NullReferenceException:当一个空对象被引用时运行时引发。
InvalidOperationException:当对方法的调用对对象的当前状态无效时,由某些方法引发。
ArgumentException:所有参数异常的基类。
ArgumentNullException:在参数为空(不允许)的情况下,由方法引发。ArgumentOutOfRangeException:当参数不在一个给定范围之内时,由方法引发。
InteropException:目标在或发生在CLR外面环境中的异常的基类。ComException:包含COM类的HRESULT信息的异常。
SEHException:封装Win32结构异常处理信息的异常。
SqlException:封装了SQL操作异常。
常见具体的异常对象:
ArgumentNullException一个空参数传递给方法,该方法不能接受该参数ArgumentOutOfRangeException参数值超出范围
ArithmeticException出现算术上溢或者下溢
ArrayTypeMismatchException试图在数组中存储错误类型的对象
BadImageFormatException图形的格式错误
DivideByZeroException除零异常
DllNotFoundException找不到引用的DLL
FormatException参数格式错误
IndexOutOfRangeException数组索引超出范围
InvalidCastException使用无效的类
InvalidOperationException方法的调用时间错误
NotSupportedException调用的方法在类中没有实现
NullReferenceException试图使用一个未分配的引用OutOfMemoryException内存空间不够
StackOverflowException堆栈溢出
第三篇:C#面向对象学习心得
一、封装
这是一种隐藏信息的特性。拿本节引例来说,类CalculateDate 将数据结构与算法隐藏在类的内部,外界使用者无需知道具体技术实现细节即可使用此类。封装这一特性不仅大大提高了代码的易用性,而且还使得类的开发者可以方便地更换新的算法,这种变化不会影响使用类的外部代码。可以用以下公式展示类的封装特性:封装的类=数据+对此数据所进行的操作(即算法)。通俗地说,封装就是:包起外界不必需要知道的东西,只向外界展露可供展示的东西。在面向对象理论中,封装这个概念拥有更为宽广的含义。小到一个简单的数据结构,大到一个完整的软件子系统,静态的如某软件系统要收集数据信息项,动态的如某个工作处理流程,都可以封装到一个类中。具备这种“封装”的意识,是掌握面向对象分析与设计技巧的关键。
二、继承
继承是面向对象编程中一个非常重要的特性,它也是另一个重要特性——多态的基础。现实生活中的事物都归属于一定的类别。在一些书中,将父类称为超类(super class)。“继承”关系有时又称为“派生”关系,“B 继承自A”,可以说为“B 派生自A”,或反过来说,“A 派生出B”。父类与子类之间拥有以下两个基本特性:
(1)是一种(IS-A)关系:子类是父类的一种特例。
(2)扩充(Extends)关系:子类拥有父类所没有的功能。
1.类成员的访问权限
面向对象编程的一大特点就是可以控制类成员的可访问性。当前主流的面向对象语言都拥有以下三种基本的可访问性:
(1)公有 public 访问不受限制。
(2)私有 private 只有类自身成员可以访问。
(3)保护 protected 子类可以访问,其他类无法访问。
由此可见,可以通过子类对象访问其父类的所有公有成员,事实上,外界根本分不清楚对象的哪些公有成员来自父类,哪些公有成员来自子类自身。小结一下继承条件下的类成员访问权限:
(1)所有不必让外人知道的东西都是私有的。
(2)所有需要向外提供的服务都是公有的。
(3)所有的“祖传绝招”,“秘不外传”的都是保护的。
C#中还有一种可访问性,就是由关键字internal 所确定的“内部”访问性。internal 有点像public,外界类也可以直接访问声明为internal 的类或类的成员,但这只局限于同一个程序集内部。
读者可以简单地将程序集理解为一个独立的DLL 或EXE 文件。一个DLL 或EXE 文件中可以有多个类,如果某个类可被同一程序集中的类访问,但其他程序集中的类不能访问它,则称此类具有internal 访问性。internal 是C#的默认可访问性,这就是说,如果某个类没有任何可访问性关键字在它前面,则它就是internal 的。
2.子类父类变量的相互赋值
子类对象可以被当成基类对象使用。这是因为子类对象本就是一种(IS_A)父类对象,因此,以下代码是合法的:
Parent p;
Son c = new Son();
p = c;
然而,反过来就不可以,父类对象变量不可以直接赋值给子类变量。如果确信父类变量中所引用的对象的确是子类类型,则可以通过类型强制转换进行赋值,其语法格式为: 子类对象变量=(子类名称)基类对象变量;
子类对象变量=基类对象变量 as 子类名称;
3.方法重载、隐藏与虚方法调用
由于子类对象同时汇集了父类和子类的所有公共方法,而C#并未对子类和父类的方法名称进行过多限制,因此,一个问题出现了:如果子类中某个方法与父类方法的签名一样(即方法名和方法参数都一样),那当通过子类对象访问此方法时,访问的是子类还是父类所定义的方法?让我们先从子类方法与父类方法之间的关系说起。总的来说,子类方法与父类方法之间的关系可以概括为以下三种:
(1)扩充(Extend):子类方法,父类没有;
(2)重载(Overload):子类有父类的同名函数,但参数类型或数目不一样;
(3)完全相同:子类方法与父类方法从方法名称到参数类型完全一样。
当子类与父类拥有完全一样的方法时,称“子类隐藏了父类的同名方法,当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。“new”关键字明确告诉C#编译器,子类隐藏父类的同名方法,提供自己的新版本。如果子类隐藏了父类的同名方法,要在子类方法的实现代码中调用父类被隐藏的同名方法时要使用base 关键字。如果子类隐藏了父类的同名方法,不进行强制转换,就无法通过父类变量直接调用子类的同名方法,哪怕父类变量引用的是子类对象。这是不太合理的。我们希望每个对象都只干自己职责之内的事,即如果父类变量引用的是子类对象,则调用的就是子类定义的方法,而如果父类变量引用的就是父类对象,则调用的是父类定义的方法。这就是说,希望每个对象都“各人自扫门前雪,莫管他人瓦上霜”。为达到这个目的,可以在父类同名方法前加关键字virtual,表明这是一个虚方法,子类可以重写此方法:即在子类同名方法前加关键字override,表明对父类同名方法进行了重写。所以,将父类方法定义为虚方法,子类重写同名方法之后,通过父类变量调用此方法,到底是调用父类还是子类的,由父类变量引用的真实对象类型决定,而与父类变量无关!很明显,“虚方法调用”特性可以让我们写出非常灵活的代码,大大减少由于系统功能
扩充和改变所带来的大量代码修改工作量。由此给出结论:面向对象语言拥有的“虚方法调用”特性,使我们可以只用同样的一个语句,在运行时根据对象类型而执行不同的操作。
三、抽象
1.抽象类与抽象方法
在一个类前面加上“abstract”关键字,此类就成为了抽象类。对应地,一个方法类前面加上“abstract”关键字,此方法就成为了抽象方法。注意抽象方法不能有实现代码,在函数名后直接跟一个分号。抽象类专用于派生出子类,子类必须实现抽象类所声明的抽象方法,否则,子类仍是抽象类。抽象类一般用于表达一种比较抽象的事物,而抽象方法则说明此抽象类应该具有的某种性质,从同一抽象类中继承的子类拥有相同的方法(即抽象类所定义的抽象方法),但这些方法的具体代码每个类都可以不一样。抽象类不能创建对象,一般用
它来引用子类对象。一个抽象类中可以包含非抽象的方法和字段。因此:包含抽象方法的类一定是抽象类,但抽象类中的方法不一定是抽象方法。除了方法可以是抽象的之外,属性也可以是抽象的。
2.接口
接口可以看成是一种“纯”的抽象类,它的所有方法都是抽象方法。抽象类定义了对象所属的类别,而接口实际上定义了一种对象应具有的行为特性。某个类可以实现多个接口,当创建一个此类的对象之后,通过引用这个对象的对象变量可以访问其所有的公有方法(包括自身的公有方法以及由接口定义的公有方法以)。在这种情况下,根本分不清哪些方法是由接口定义的,哪些是由类自己定义的。C#提供了一种“显式接口”实现机制,可以区分开这两种情况。由此得到一个结论:如果一个类显式实现某个接口,则只能以此接口类型的变量为媒介调用此接口所定义的方法,而不允许通过类的对象变量直接调用。或者这样说:被显式实现的接口方法只能通过接口实例访问,而不能通过类实例直接访问。
四、多态
方法重载属于多态的一种,两个构成重载关系的函数必须满足几个条件:函数名相同、参数类型不同,或参数个数不同。具体调用哪个方法要看参数,需要注意的是,方法返回值类型的不同不是方法重载的判断条件。多态编程的基本原理是:使用基类或接口变量编程。在多态编程中,基类一般都是抽象基类,其中拥有一个或多个抽象方法,各个子类可以根据需要重写这些方法。或者使用接口,每个接口都规定了一个或多个抽象方法,实现接口的类根据需要实现这些方法。因此,多态的实现分为两大基本类别:继承多态和接口多态。
1.接口多态与继承多态
接口多态与继承多态其编程方式与作用都是类似的。但由于一个类可以实现多个接口,所以,接口多态较继承多态更灵活,因而在编程中也用得更广。多态是面向对象技术中最精华的部分之一。大量的精巧软件设计方案都建立在对多态特性的巧妙应用上。在编程中应用多态,可以将其简化为两句:应用继承实现对象的统一管理;应用接口定义对象的行为特性。对比传统的不使用多态的编程方式,使用多态的好处是:当要修改程序并扩充系统时,需要修改的地方较少,对其他部分代码的影响较小。
五、类与对象
类是面向对象编程的基本单元,与使用C语言等结构化编程语言不一样,使用C#编程,所有的程序代码几乎都放在类中,不存在独立于类之外的函数。一个类可以包含两种成员:静态成员和实例成员,静态成员是供类的所有对象所共享的,而实例成员只供某一个对象所有。实例成员与静态成员的访问规则:位于同一类中的实例方法可直接相互调用;类的字段(包括实例字段和静态字段)可以被同一类中的所有实例方法直接访问;类中的静态方法只能直接访问类静态字段。
类中包括:方法和字段,属性是一种特殊的字段,它可以保证数据的合法性,方法和字段这两个概念是面向对象理论的术语,是通用于各种面向对象语言的。字段(Field)代表了类中的数据,在类的所有方法之外定义一个变量即定义了一个字段。在变量之前可以加上public、private 和protected 表示字段的访问权限。方法(function)功能代码的集合,在程序开发过程中,经常发现多处需要实现或调用某一个公用功能,这些功能的实现都需要书
写若干行代码。如果在调用此功能的地方重复书写这些功能代码,将会使整个程序中代码大量重复,会增大开发工作量,增加代码维护的难度。为了解决代码重复的问题,绝大多数程序设计语言都将完成某一公用功能的多个语句组合在一起,起一个名字用于代表这些语句的全体,这样的代码块被称为“函数(function)”。引入“函数”概念之后,程序中凡需要调用此公用功能的地方都可以只写出函数名,此名字就代表了函数中所包含的所有代码,这样一来,就不再需要在多个地方重复书写这些功能代码。
对象是以类为模板创建出来的。类与对象之间是一对多的关系。在C#中,使用new 关键字创建对象。在程序中“活跃”的是对象而不是类。在面向对象领域,对象有时又被称为是“类的实例”,“对象”与“类的实例”这两个概念是等同的。
六、值类型与引用类型
1.值类型
值类型变量与引用类型变量的内存分配模型也不一样。每个正在运行的程序都对应着一个进程(process),在一个进程内部,可以有一个或多个线程(thread),每个线程都拥有一块“自留地”,称为“线程堆栈”,大小为1M,用于保存自身的一些数据,比如函数中定义的局部变量、函数调用时传送的参数值等,这部分内存区域的分配与回收不需要程序员干涉。所有值类型的变量都是在线程堆栈中分配的。值类型共有三种:简单类型、枚举类型和结构类型。
2.引用类型
另一块内存区域称为“堆(heap)”,在.NET 这种托管环境下,堆由CLR 进行管理,所以又称为“托管堆(managed heap)”。用new 关键字创建的类的对象时,分配给对象的内存单元就位于托管堆中。在程序中我们可以随意地使用new 关键字创建多个对象,因此,托管堆中的内存资源是可以动态申请并使用的,当然用完了必须归还。打个比方更易理解:托管堆相当于一个旅馆,其中的房间相当于托管堆中所拥有的内存单元。当程序员用new 方法创建对象时,相当于游客向旅馆预订房间,旅馆管理员会先看一下有没有合适的空房间,有的话,就可以将此房间提供给游客住宿。当游客旅途结束,要办理退房手续,房间又可以为其他旅客提供服务了。引用类型共有四种:类类型、接口类型、数组类型和委托类型。所有引用类型变量所引用的对象,其内存都是在托管堆中分配的。严格地说,我们常说的“对象变量”其实是类类型的引用变量。但在实际中人们经常将引用类型的变量简称为“对象变量”,用它来指代所有四种类型的引用变量。
七、命名空间与类库
1.命名空间
在使用面向对象技术开发的现代软件系统中,经常拥有数百甚至上千个类,为了方便地管理这些类,面向对象技术引入了“命名空间(namespace)”的概念。命名空间可以看成是类的“容器”,它可以包含多个类。.NET Framework 使用命名空间来管理所有的类。如果把类比喻成书的话,则命名空间类似于放书的书架,书放在书架上,类放在命名空间里。当我们去图书馆查找一本书时,需要指定这本书的编号,编号往往规定了书放在哪个书库的哪个书架上,通过逐渐缩小的范围:图书馆->书库->书架,最终可以在某个书架中找到这本书。类似地,可以采用图书馆保存图书类似的方法来管理类,通过逐渐缩小的范围:最大的命名空间->子命名空间->孙命名空间„„,最终找到一个类。
2.类库
为了提高软件开发的效率,人们在整个软件开发过程中大量应用了软件工程的模块化原则,将可以在多个项目中使用的代码封装为可重用的软件模块,其于这些可复用的软件模块,再开发新项目就成为“重用已有模块,再开发部分新模块,最后将新旧模块组装起来”的过程。整个软件开发过程类似于现代工业的生产流水线,生产线上的每个环节都由特定的人员负责,整个生产线上的工作人员既分工明确又相互合作,大大地提高了生产效率。在组件化开发大行其道的今天,人们通常将可以重用的软件模块称为“软件组件”。在全面向对象的.NET 软件平台之上,软件组件的表现形式为程序集(Assembly),可以通过在Visual Studio 中创建并编译一个类库项目得到一个程序集。在Visual Studio 的项目模板中,可以很方便地创建类库(Class Library)项目,Visual Studio 会自动在项目中添加一个名为Class1.cs 的类文件,程序员可在此类文件中书写代码,或者添加新的类。一个类库项目中可以容纳的类数目没有限制,但只有声明为public 的类可以被外界使用。类库项目编译之后,会生成一个动态链接库(DLL:Dynamic Link Library)文件。这就是可以被重用的.NET 软件组件——程序集。默认情况下,类库文件名就是项目名加上“.dll”后缀。每个类库项目都拥有一个默认的命名空间,可以通过类库项目的属性窗口来指定。需要仔细区分“类库项目”、“程序集”和“命名空间”这三个概念的区别:
(1)每个类库项目编译之后,将会生成一个程序集。
(2)类库项目中可以拥有多个类,这些类可属于不同的命名空间。
(3)不同的类库项目可以定义相同的命名空间。
根据上述三个特性,可以得到以下结论:“命名空间”是一个逻辑上的概念,它的物理载体是“程序集”,具体体现为“DLL”(或EXE)文件。在Visual Studio 中,可通过创建“类库”类型的项目生成程序集。一个程序集可以有多个命名空间,而一个命名空间也可以分布于多个程序集。一旦生成了一个程序集,在其他项目中就可以通过添加对这一程序集的引用而使用此程序集中的类。其方法是在“项目”菜单中选择“添加程序集”命令,激活“浏览”卡片,选择一个现有的程序集文件(DLL 或EXE)。一个项目添加完对特定程序集的引用之后,就可以直接创建此程序集中的类了,当然要注意指明其命名空间。
八、委托
委托是一种新的面向对象语言特性,在历史比较长的面向对象语言比如C++中并未出现过。微软公司在设计运行于.NET Framework平台之上的面向对象语言(如C#和VisualBasic.NET)时引入了这一新特性。委托(delegate)也可以看成是一种数据类型,可以用于定义变量。但它是一种特殊的数据类型,它所定义的变量能接收的数值只能是一个函数,更确切地说,委托类型的变量可以接收一个函数的地址,很类似于C++语言的函数指针。简单地说:委托变量可看成是一种类型安全的函数指针,它只能接收符合其要求的函数地址。委托可以看成是一个函数的“容器”,将某一具体的函数“装入”后,就可以把它当成函数一样使用。定义委托类型时对函数的要求被称为函数的“签名(signature)”。函数的签名规定了函数的参数数目和类型,以及函数的返回值,体现了函数的本质特征。每一个委托都确定了一个函数的签名。拥有不同签名的函数不能赋值给同一类型的委托变量。因此,一个委托类型的变量,可以引用任何一个满足其要求的函数。
1.委托的组合与分解
委托变量可以代表某一函数,使用委托变量就相当于调用一个函数。如果仅是这么简单,那么直接调用函数不就行了吗?为什么还要引入“委托”这一特性?事实上,委托不仅可以代表一个函数,还可以组合“一堆”的函数,然后批量执行它们,这样的委托变量又称为“多路委托变量”。可以用加法运算符来组合单个委托变量为多路委托变量。类似地,也可以使用减法运算符来从一个多路委托变量中移除某个委托变量。
2.事件与多路委托
事件的主要特点是一对多关联,即一个事件源,多个响应者。在具体技术上,.NET Framework 的事件处理机制是基于多路委托实现的。事件与多路委托其实大同小异,只不过多路委托允许在事件源对象之外激发事件罢了。所有的.NET Framework 可视化窗体控件的预定义事件,都是某一对应的“事件名+Handler”委托类型的变量。与此事件相关的信息都封装在“事件名+Args”类型的事件参数中,此事件参数有一个基类EventArgs,它是所有事件参数的基类。明了上述内部机理,对于我们在程序中定义自己的事件非常有好处,尤其是开发一个自定义的可视化控件时,如果需要增加新的事件类型,我们应尽量遵循.NET Framework 的定义事件的框架,给事件取一个名字,定义一个“事件名+Handler”的事件委托类型,再从EventArgs 派生出自定义事件的参数,取名为“事件名+Args”。
面向对象的软件系统有许多都是事件驱动的,ASP.NET 就采用了“事件驱动”的编程方式。所谓“事件驱动”的开发方式,就是指整个系统包含许多的对象,这些对象可以引发多种事件,软件工程师的主要开发工作就是针对特定的事件书写代码响应它们。.NET 事件处理机制建立在委托的基础之上,而这两者都是ASP.NET 技术的基础之一。因此,必须牢固地掌握好委托和事件这两种编程技术,才能为掌握ASP.NET 技术扫清障碍。
第四篇:C#总结
引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的。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 一元操作符 +位移操作符 << >> 关系操作符 < > <= >= 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!~ ++--true false 二元:+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
第五篇:c#读书笔记
1、.NET平台包括.NET框架和.NET开发工具等组成部分。.NET框架是整个开发平台的基础,包括公共语言运行库和.NET类库。.NET开发工具包括Visual Studio.NET集成开发环境和.NET编程语言。.NET框架(.NET Framework)是.NET开发平台的基础。.NET框架提供了一个跨语言的、统一的、面向对象的开发和运行环境。
2、在Visual Studio.NET集成开发环境下,可以开发多种不同类型的应用程序。最常见的有以下几种。
。控制台应用程序
。Windows应用程序
。ASP.NET网站
3、开发和运行控制台应用程序
创建一个控制台应用程序,主要包含以下步骤:
(1)执行文件—》新建—》项目
(2)打开“新建项目”对话框,在“项目类型”列表中选择Visual c#节点下的Windows,在“模板”窗格中选择“控制台应用程序”项目模板,输入项目的名称、位置及
解决方案名称后,单击“确定”按钮。
(3)在打开的.cs文件中编写代码。
(4)运行程序。执行“调试”—》启动调试菜单命令,编译并运行程序
4、c#程序的基本结构
。using关键字的功能是用于导入其它命名空间中定义的类型,包括.NET类库。例如,代码中使用的console.readline方法实际上是一个简写,其全称是system.console.readline,但由于在代码的开始使用using指令引入了system命名空间,所以后面可以直接使用console.readline来进行输入。
。namespace 即“命名空间”,也称“名称空间”。命名空间是Visual Studio.NET中的各种语言使用的一种代码组织的形式,当编译一个解决方案时,系统会用项目名称做名字,生成一个namespace,并把类都放在这个namespace里面。