一、变量与表达式

       1.变量第一个字母必须是字符、下划线或@,其后就可以字符、数字、下划线。

       2.字面值

       (1)double为浮点默认字面值,在给float或decimal浮点型赋值时要加f/F或m/M。

       3。表达式

       Char变量实际存储的是数字,所以把两个char变量加在一起会得到一个数字。

二、流程控制

      

       2.Switch

       在C#中,执行完一个Case语句后,不能进入下一个Case语句,而就加个break或return 或goto语句,如若不然会报错!但是有个例外,如果把多个Case语句放在一起,但是前面的Case语句没有执行体,只有最后一个Case语句有执行体,实际是一次检查多个条件。也即只要其中有一个符合,那么最后一个Case语句的执行体就会执行!

       例:

       Case <comparison X>:

       Case <compareson Y>:

                     <code>

                     Break;

这里的X或Y必须是常量(字面值或字符常量)。

       3.foreach语句

       可以用foreach来查找string,可以用索引访问其字符。

       Froeach(char c in mStrings){}

三、变量的更多内容

       1.显式转换

       (1)用Checked和uncheked检测表达式的溢出,也就是将数据类型转换为另一种数据类型时,可以知道是否有数据丢失。

       (2)Convert命令显示转换时,它总是检测溢出的。

       (3)比Int类型低的整数类型(char,byte,shor等),进行运算后提升为int类型,所以必须用int或Long等长整数类型来保存结果(当然用浮点也可以的)。而针对float和double不会出现这种情况,float运算可以用float类型存值。

       (4)变量在赋值后,这个变量才拥有内存空间,如果这种占据内存空间插座在循环中发生,该值实际上定义了一个局部值。在循环外部会超出作用域。一般情况下,最好在声明和初始化所有的变量后,再在代码中使用它们。

      

四、数组

       1.数组声明和初始化

       由于数组是引用类型,所以应该用new来初始化数组。

       (1)一维

              Int[] a={1,2};//这样也可以的。

              Int[] a=new int[2]{1,2};//定大小(元素个数与数组大小必须匹配)

              Int[] a=new int[]{2,3,3};//由值定义大小

              Int[] a=new int[size];//常量定义

       Const int size=3;

       (2)多维

              Int[,] a=new int[2,2];

              Int[,] a={{1,2},{2,3}};//

              与C不同的是用数组初始化器(用字面值初始化)时,必须指定每个元素的值,不能遗漏。              针对一维数组可以用foreach来循环访问每个元素。

              Foreach(int I in myInt){}

       (3)变长数组

       注意:子数组是不能指定的,而必须分开指定。

       例:Int[][] a;

              a=new int[2][];

              a[0]=new int[4];

              a[1]=new int[5];

              或 a=new int[2][]{new int[]{1,2},new int[]{3,4}};

       例:int[][] a={new int[]{1,2,3},new int[]{3,3,3}};

       2Array类

       此类是作为所有数组的虚基类,当用C#语法声明了数组,实际上就继承了它的方法和属性。

       属性                                  说明

       Length                               返回元素个数,返回int型,如果多维数组,返回所                                           有       阶的元素个数

       Longlength                        返回元素个数,只是用Long型返回的。

       Rank                                  返回数组的维数

       常用方法

       CreateInstance(),SetValue(),Sort()

       3.迭代器

       迭代器的定义是,它是一个代码块,按顺序提供了要在foreach循环中使用的所有值,一般情况下,这个代码块是方法,但也可以使用属性访问器和其代码块作为迭代器。

       (1)如果要迭代一个类,使用方法GetEnumerator(),其返回类型是IEnumerator就可以了。

       (2)如果要迭代一个类成员,例如方法,则返回类型IEnumerable。

       在迭代器块中,使用yeild关键字选择要在foreach循环中使用的值。可以使用yield return;来中止返回值。

       Public static IEnumerable SimpleList()//迭代一个类成员

{

       Yield return “str 1” ;

       Yield return “str 2” ;

}

       Foreach(string item in SimpleList){}

       Public IEnumerator GetEnumerator()//如果类中存在这个方法并且返回相应接口就可以迭代一个类。一般类重写IEnumerable接口来实现,泛型和非泛型都实现

{

       Yield return “ok”;

}

五、类

       1.定义类

       (1)类型访问修饰符只能是public、internal(只是程序集中可以访问),如果没有加可访问性修饰符,默认为internal。

       (2)类的抽象(abstract)或密封(sealed)

       (3)不允许派生类的可访性比基类更高,也即,内部类可以继承公共类,公共类不能继承内部类。

       (4)当类既要继承基类也实现接口时,必须把基类写在前,接口放在后。

              例如public class AClass:BClass,Itest

       2.构造函数

       (1)普通构造函数可以有访问修饰符。若有把构造函数设置为私有的,它在以下两种情况下有用的:

              类仅为某些静态成员或属性的容器,因此永远不会实例化。

              希望类仅通过调用某个静态成员函数来实例化。

       (2)若定义了带有参数的构造函数,编译器不会自动创建默认的无参构造函数,只有没有显示定义任何构造函数时,编译器才提供无参构造函数。

       (3)静态构造函数

       作用:给类中静态成员执行更复杂的初始化。

       一个类中只能有一个静态构造函数,该构造函数不能有参数,不能有访问修饰符,它不能直接调用 ,只能访问静态类型成员,只能下述情况调用:

       1)创建包含该静态构造函数的类实例时。

       2)访问包含静态构造函数的类的静态成员时。

       这两种情况都会先调用静态构造函数,然后执行其它的。无论创建了多了该类型实例,其静态构造函数只调用一次。

       (4)构造函数执行序列

       1)无论使用什么构造函数实例化一个类,总是先调用System.object.object()

       2)除非明确指定,否则就使用基类默认构造函数。

       3)显示调用基类的构造函数用base关键字,例:

              Public MyClass(int I,int j):base(2){}

       或public MyClass(int I,int j):base(i){}

       4)本类构造函数调用本类另一个构造函数。

              Public MyClass():this(6,7){}

              Pulibc MyClass(int I,int j):base(i){}

       当使用MyClass()构造函数时,先执行System.object.object(),接着执行基类带一个参数的构造函数,再执行本类型带两个参数的MyClass(int I,int j),最后执行MyClass()。

       3.成员定义

       类中的数据成员有默认值。如果没有显示声明成员的可访问性,编译器通常默认选择private(限制最大的那个)

       (1)访问修饰符public,private,interal,protected适用于方法、字段、属性、事件.

       (2)字段、属性、方法、事件都能用static声明。

       (3)字段若加上readonly关键字,表示它只能由初始化语句或只能在非静态构造函数中赋值。(只加readonly属于实例字段)

       (4)用const声明字段后,static就不能再加,否则报错。它们都是类级别,都是静态的存储

                     private static string connectionString;可以在构造函数(静态和非静态)或属性或初始化语句中赋值!

                     private const string connectionString;只能用初始化语句赋值!

                     private static readonly string connectionString;这样的字段也只能在初始化语句中或静态构造函数中赋值,不能在非静态构造函数或属性中赋值!

             

       (5)静态方法只能访问静态方法、静态属性、静态字段,返过来非静态成员是可以访问静态成员的。

       (6)以下关键字不能用于静态方法或静态属性,想想就知道了为什么!

              virtual-方法可重写。

              Abstrct-方法必须在非抽象类中重写(只能用于抽象类中)

              Override-方法重写了一个基类方法(方法被重写,必须使用)

              Extern-方法定义放在其它地方

              NEW-显示说明隐藏基类方法

       其中virtual Abstrct Override可以用在属性中( 不是属性块)。

       (7)如果重写了一个方法,可以用sealed指定派生类中不能对这个方法作进一步的修改。也即孙子类不能再重写此方法,本来是可以重写的。

       (8)显示的调用基类的方法使用base关键字。

       例:

              Public override void DoSomething()

              {

                     Base.Dosomething();

              }

       由于base关键字是对象的实例,不能在静态方法中使用,同样也经常使用this关键字在方法中传递调用对象实例。

       (9) protected internal:在同一个Assembly中,他的访问级别和public一样,而跨Assembly访问时,他的访问级别同protected一样。即protected的范围 + internal的范围。

    (10)一个派生类型重写在它的基类中定义的一个成员时,C#编译器要求原始成员和重写成员具有相同的可访问性。也即,如果基类中的成员是protected的,派生类是的重写成员也必须是protected的。

       4.方法

       (1)引用参数(ref和out)

       用ref时,在方法调用和方法声明中都要指定reft,并且传递的变量必须是初始化后来变量,否则出错。

       用out时,首先,传递的变量可以事先没有赋值,其次,在方法中,该参数被看作没有赋值的,因此,在方法中必须对此参数赋值,否则出错(而ref不必须赋值)。

       (2)参数数组

       这个参数必须是函数定义听最后一个参数,它可以使用个数不定要参数调用函数,用params关键字定义。

       例:void fun(params <type>[] <name>)

       注意:如果给参数数组传个数组,那么就不能再传递任何参数了,否则报错,当然调用此函数时也可以不传递任何参数。

       5.属性

       (1)自动实现属性

       这种属性会自动实现基础成员变量。如

       Public stirng ForeName(get;set;)

       不需要声明private string forename,编译器会自动实现它。而且这两个访问器(get,set)必须成对出现,缺一个不可。

       (2)属性中的get或set块儿可以使用protected或private修饰符,public不可以,因为这两个访问器修饰符必须比属性具有更强的限制。

      

       6.结构

       (1)结构和类的区别

              类和结构的区别是它们在内存中的存储方式不同(类是存储在堆上的引用类型,而结构是存储在栈里的值类型)

              结构不支持继承

              构造函数的工作方式有一些区别。尤其是编译器总是提供一个无参的默认构造函数,这是不允许替换的。

              使用结构,可以指定如何在内存中布局。

       (2)结构是值类型

       结构是值类型那么就有值类型的特征

       例

       Struct Test{pubic int width;public int height;}

       Test t;

       T.width=2;

       T.height=3;

       如果Test是个类,这种声明后没有分配空间就为其数据成员赋值是会出错的,但对结构是值类型,在声明时就已经在栈里分配了空间,因此可以为其成员赋值了。

       如果结构作为了类的数据成员,那么结构变量的数据成员就具有默认值。和其它的预定义类型是一样的。

       (3)结构和继承

       结构不能继承的,但是每个结构派生于System.valueType,System.ValueType派生于于System.object.因此可以重写object的方法.

        (4)我们不能像类那样显示去为结构定义无参构造函数,那是编译器提供的。

     (5)

  • class是引用类型,继承自System.Object类;struct是值类型,继承自System.ValueType类,因此不具多态性。但是注意,System.ValueType是个引用类型。
  • 从职能观点来看,class表现为行为;而struct常用于存储数据。
  • class支持继承,可以继承自类和接口;而struct没有继承性,struct不能从class继承,也不能作为class的基类,但struct支持接口继承(记得吗,《第二回:对抽象编程:接口和抽象类》也做过讨论)
  • class可以声明无参构造函数,可以声明析构函数;而struct只能声明带参数构造函数,且不能声明析构函数。因此,struct没有自定义的默认无参构造函数,默认无参构造器只是简单地把所有值初始化为它们的0等价值
  • 实例化时,class要使用new关键字;而struct可以不使用new关键字,如果不以new来实例化struct,则其所有的字段将处于未分配状态,直到所有字段完成初始化,否则引用未赋值的字段会导致编译错误。
  • class可以实抽象类(abstract),可以声明抽象函数;而struct为抽象,也不能声明抽象函数。
  • class可以声明protected成员、virtual成员、sealed成员和override成员;而struct不 可以,但是值得注意的是,struct可以重载System.Object的3个虚方法,Equals()、ToString()和 GetHashTable()。
  • class的对象复制分为浅拷贝和深拷贝(该主题我们在本系列以后的主题中将重点讲述,本文不作详述),必须经过特别的方法来完成复制;而struct创建的对象复制简单,可以直接以等号连接即可。
  • class实例由垃圾回收机制来保证内存的回收处理;而struct变量使用完后立即自动解除内存分配。
  • 作为参数传递时,class变量是以按址方式传递;而struct变量是以按值方式传递的。

       7.静态类

       只能包含静态成员。可以有静态构造函数。

      

六、接口

       1.定义接口

       (1)类型访问修饰符只能是public、internal(只是项目中的代码可以访问)或没有。

       (2)关键字abstract或sealed不能使用。

       2.接口实现

       (1)一个接口要隐藏基接口的成员,可以使用new来实现。

       (2)接口中可以定义属性(相当于方法)。

       (3)接口成员定义:

              不允许使用任何访问修饰符,全部都是公共的。

              接口成员不能包含代码体。

              接口成员不通用static,virtrul,abstract或sealed来定义

              接口不能定义字段成员

              类型定义成员是禁止的

       (4)类不论是实现接口还是继承基类,重写方法时都不能修改其原有的访问修饰符

       (5)实现接口的方法时可用virtual或abstract,其它的都不能用。实现接口方法时override都不能用,否则出错!!

       (6)显示执行接口成员

       例:

    

Public class MyClass:IMyInterface 
  
       {     
              Void iMyInterface.DoSomeThing()//显示实现 
  
              { 
  
              } 
  
              Public void DoSomeThing(){}//隐式 
  
       }

       显示的执行接口成员,只能通过接口访问,而隐式的类和接口都行。

       即:

              MyClass my=new MyClass();

              IMyInterface myInt=my;

              MyInt.DoSomething();//接口访问

              My.DoSomeThing();//类访问

       实际上显示实现接口方法时,类实例根本无法调用此方法。

       (7)接口定义属性时,可以只有一个set或get块,在类实现时,那么是可以添加缺少的那个块的,不过其访问属性要加强,比接口中定义的访问器的访问必更严格。

接口小结:接口可以继承基接口。它只能有方法和属性。在方法和属性前通常不能有任何关键字(派生接口要隐藏其接口的方法时可以用new除外)。

七.继承

       1.继承类型

       实现继承:一个派生于一个基类型。

       接口继承:一个类实现接口的方法。

       2.结构总是派生于System.ValueType,它们还可派生于任意多个接口。

        类总是派生于用户选择的另一个类,这们还可以派生于任意多个接口。

八、运算符和强制类型转换

       测试类型用typeof()运算符或用对象的GetType()方法。

       1.布尔逻辑

       (&,|)这两个作为逻辑运算符时,它两边的操作数总要计算的。

       2.封箱和拆箱

       封箱就是把值类型转换为对象类型或其实现的接口类型,拆箱就是相反的过程。

       例:

        Int b=2;

       Object val=b;//封箱,自动的,

       Int c=(int)val;//拆箱,显示的

       在装箱和拆箱的过程中,这两个过程都会数据复制到新装箱和拆箱的对象上,这样不会影响原来的值类型的内容。

       封箱的作用主要体现在两方面,一个把值类型加入到集合中,如ArrayList,集合中的项是object,其次,有一个内部机制允许在值类型上调用相应对象的方法和属性,如整型和结构。例, 10.toString();这里把10先装箱为一个临时对象,然后调用该对象的方法。

       3.is运算符

       <opernand> is <type>

       这个表达式结果如下:

       (1)如果<type>是一个类类型,而<operand>也是该类类型,或是继承该类类型,或可以封箱到该类型中,则为true..

       (2)如果<type>是一个接口类型,而<operand>实现了该接口的类类型,或者也是该类型。则为true

        (3)如果<type>是一个值类型,而<operand>也是该类型,或者可以拆箱到该类型。则为true

       4.as运算符

       把一种类型转换为引用类型。

       <operand> as <type>

       (1)如果<operand>类型是<type>类型

       (2)<operand>类型可以隐式转换为<type>类型

       (3)<operand>可以封箱到<type>类型。

       如果不能从<operand>转换为<type>类型,则表达式的结果为null.

       5.可空类型?

       通常可空类型与一元或二元运算符一起使用,如果其中一个操作数或两个操作数为null,其结果为null.    例:

       Int? a=null;

       Int? b=a+3;//b=null

       可空类型的运算结果还是可空类型,如果用非可空类型来接收结果值会出错。

       Int? a=2;

       Int resul=a*2;//报错

       可用强制类型转换

       Int result=(int)a*2;

       在比较可空类型时,只要有一个操作数为null,结果为false。

       6.空接合运算符??

       第一个操作数必须是可空类型或引用类型,第二个操作数必须与第一个操作数类型相同,或可以隐式转换为第一个操作数类型。

       运算方法如下:如果第一个操作数不是null,则整个表达式的值等于第一个操作数的值,否则整个表达式的值为第二个操作数的值。运算结果可以用非可空类型变量来接收值!

       Int?a =10;

       Int b=a ?? 2;//b=10

       Int? a=null;

       Int b=a ?? 3;//b=3

       7.类型转换

       短字节向长字节类型可以隐式转换,反之必须显示转换。

       在隐式转换值类型时,可空类型要额外考虑。

       (1)可空类型转换为其它可空类型时遵循短字节向长字节的转换规则。

       (2)非可空类型转换为可空类型时遵循短字节向长字节的转换规则。

       (3)可空类型不能隐式转换为非可空类型,必须显示转换。

       8.运算符重载

              (1)重载的运算符是公共和静态的函数。

                     例:

                     Public static Vector operator +(Vector v1,Vector v2)

              (2)比较运算符重载

               ==和!=,<,>,>=,<=,true,false

              这些运算符要求必须成对的重载。而且返回值必须为bool类型。

       9.用户定义数据类型转换(类和结构)

       例:

       Public static implicit operator float(Currency value){}

       Public static explicit operator Currency(float value){}

       这里有两个关键字可以指定这种转换是显示的还是隐式的!紧接着在operator关键字后的  数据类型表式转换的目标类型。上例中Currency是一个结构。

       (1)类之间的数据类型转换

       如果一个类直接或间接继承了另一个类,就不能定义这两个类之间的数据类型转换(这些类型的已经存在)。

       数据类型转换必须在源或目标数据类型的内部定义。

       一旦在一个类的内部定义数据类型转换,就不能在另一个类中定义相同的数据类型转换。

九、C#3.0语言的改进

       1.类初始器 

       语法:

     

ClassName variableName=new classname{ 
  
              PropertyOfField1=value1, 
  
              PropertyOfField2=value2, 
  
              。。。。。。 
  
       }

       使用对象初始器时,不能明确调用类的构造函数,而是自动调用默认的无参构造函数。这个调用是在初始化器设置参数之前调用的,所以必须能访问类的默认构造函数。

       2.集合初始化器

       就是数组初始化器的一个扩展,或者说是原来在集合中没有,现在加上了。

       例:

       List<int> myint=new List<int>{2,3,3};

       3.类型推断

       使用var关键字,编译器根据变量的初始值来“推断”变量的类型。

       用var声明变量的同时必须初始化变量,否则报错

       例

       Var a=0;

       Int a=0;

       编译后上面两条语句是等价的。

       Var关键字还可以通过数组初始器来推断数组的类型。

       Var myArray=new[] {2,3,3};  

       此例会把myArray的类型被隐式地设置为int[].

       但在初始化器中使用的数组元素必须是:

              相同的类型

              相同的引用类型或为空。

              所有元素的类型都可以隐式地转换为一个类型

       元素可以转换的类型就称为数组元素的最佳类型。

       4.匿名类型

       匿名类型只是一个继承了Object的、没有名称的类。该类的定义从初始化器中推断。

       例

       Var caption=new {FirstName=”2b”,MiddleName=”3b”  };

       这会生成一个包含FirstName,MiddleName属性的对象,如果创建另一个对象

       Var doctor= new {FirstName=”2b”,MiddleName=”3b”   };

       Caption和doctor的类型是相同的。还可以使用一个已经存在的对象来初始化匿名对象。包含相应的属性FirstName,MiddleName.

       Var caption=new {person.FirstName,person.MiddleName};

       这些对象的类型是未知的,编译器为类型”伪造”一个名称 。但只编译器才能使用它。

       注意,这此属性定义为只读属性,这表示,如果要在数据存储对象中修改属性的值,就不能使用匿名类型。

十、委托和事件

       1.委托

       (1)委托就是取代了在C或C++中的把一个函数传递给另一个函数,而在C#中委托是一种特殊的对象类型。

       Delete void IntMethodInvoker(int i);

       上面就是一个委托的示例,它指定了每个委托的实例都包含了一个方法的细节,该方法有一个int参数,并返回void。编译器将在后台创建一个表示该委托的一个类。由于定义一个委托就基本上就是定义了一个类,所以既可以在类的内部定义委托,也可以在类的外部定义委托,还可以在命名空间中把委托定义为顶层对象。可以为委托加上访问修饰符:public,private,protected

       例:

    

Private delete string GetString(); 
  
       Static void main() 
  
       {     
              Int x=20; 
  
              GetString firstMethod=new GetString(x.toString);//用委托构造函数方式传递方法 
  
              或 
  
              GetString firstMethod=x.toString;//直接把方法的地址赋给委托变量 
  
              Console.write(firstmethod());//调用委托就像调用方法一样,有参传入参数 
  
       }

       以上就是定义委托和使用的委托的例子。

       注:给定委托的实例可以表示任何类型的任何对象上的实例方法或静态方法—只要方法的签名匹配委托的签名就可以了。

       (2).多播委托

       委托可以包含多个方法,这种情况下,委托只能返回void,否则,就只能得到委托调用的最后一个方法的结果。

       例:

       DoubleOp operations = MathOperations.MultiplyByTwo;

            operations += MathOperations.Square;

       operations(2.3);//它会把两个方法都调用执行了。

       在这里多播委托添加了两个操作,多播委托可以识别运算符+,+=,将方法添加到委托中,而运算符-,-=将方法从委托中删除。

       当调用多播委托时,它会把添加到委托中的所有方法全部执行一遍!但是这个执行顺序链是未定义的,也即它执行这些方法的顺序是不定的。

       多播委托包含一个逐个调用的委托集合,如果通过委托调用的一个方法抛出出了异常整个迭代停止。为了避免这个问题,应手动迭代方法列表。

       例:

     

Delegate[] delegates= operations.GetInvocationList(); 
  
       Foreach(DoubleOp d in delegates) 
  
       { 
  
       
       }

       (3).匿名方法

       例:

    

Delegate string DelegateTest(string param); 
  
       Static void 
  
    Main 
  () 
  
       {     
              String mid=”ok”; 
  
              DelegateTest test=delegate(string val) 
  
              { 
  
                     Val+=mid; 
  
                     Return val; 
  
              } 
  
              Console.write(test(“lai”));
       }

       匿名方法必须遵循两个规则:首先,匿名方法不能用跳转语句跳转到匿名方法的外部,反之亦然。其次,不能访问在匿名方法外部使用的ref 和out参数,但可以外部其它变量。

       (4)λ表达式

    λ表达式是简化匿名方法的一种方式。

        λ表达式的参数列表总是包含一个逗号分隔的列表,其中的参数要么显示类型化,要么都是隐式类型化的。如果只有一个参数,就可以省略括号,否;否则需要加上括号。

       DelegateTest my=(int paramA,int paramB)=>paramA+paramB

       或(paramA,paramB)=>paramA*paramB

       DelegateTest my =Param=>paramA-paramB

    上面的例子的语句体没有用return关键字指定返回值,根据指定的操作,编译器会推断出表达式的结果类型就是返回类型。

    还可以指定没有参数的λ表达式,但必须加上括号

    例()=>Math.Pi

    λ表达式语句体,如果语句体只有一条语句,可以去掉花括号,多条语句必须加上花括号。

    DelegateTest my =Param=>{return paramA-paramB;}

   

       (5)协变和抗变(跟C++里的很像)

       也就是说委托调用的方法不一定和委托声明定义的类型相同,有两种情况,协变和抗变。主要针对对象类型。

       (1)返回类型协变

              方法的返回类型派生于委托的返回类型。

       (2)参数类型抗变

        委托类型的参数和委托调用的方法的参数不同。委托的参数类型可能派生于方法的参数类型。

    2.事件

    (1生成事件

       例:

       Public delegate void ActionEventHandle(object sender,ActionCancelEventArgs e);

       Public static event  ActionEventHandle Action;

        注意定义一个新的事件时,要求指定与事件相关的委托,除了可以使用.net framework中定义的委托外,还可以用自定义的委托,这里就是用的自定义委托。

    除了上面那种定义事件的方法外,还可以定义添加和删除处理程序的方法。

  

Private ActionEventHandle act//先声明一个委托类型变量 
   
    Public event  ActionEventHandle Action 
  
    { 
   
       Add{ 
  
           act +=value; 
  
           } 
  
       Remove{ 
  
           act -=value; 
  
           } 
  
    }

    有点相当于把事件定义为一个属性。

    (2)调用事件

    例
    Form1.Action += new Form1.ActionEventHandler(Form1_Action);

    ActionCancelEventArgs cancelEvent = new ActionCancelEventArgs();

Action(this, cancelEvent);

上面的例子就是直接用事件变量来调用,上面调用事件就像调用方法一样,我感觉就是取代了委托来调用方法。

小结:调用自定义事件的步骤,首先,定义委托和事件,然后,用委托注册事件,其次创建自定义的事件参数对象,最后传递相关的参数给事件来触这个事件。

十一、字符串和正则表达式

    1字符串

    一般用stringbuilder执行字符串操作,string类来存储字符串或显示结果。

    2正则表达式

    .net正则表达式引擎是兼容perl5正则表达式的,加了一些新特性。

十二、泛型

    1.定义泛型类

       (1)不能假设泛型类型是什么类型,所以不能在泛型类中做某些特定类型的操作,如:

           private T1 innerT1;

           T1=new T1();//是值类型还是泛型类型?不知道,所以不能用new关键字。

       (2)注意另一个限制,在比较泛型类型提供的类型值和null时,只能使用运算符==和!=。

       由于不知道泛型类型,其它的运算符不好用的。

    2.泛型类的特性

    (1)默认值

        泛型类中不能把null赋给泛型类型,因为泛型类型既可以引用类型也可以值类型,所以这里要用default关键字赋给泛型类型,则default把null赋给引用类型,把0赋给值类型。

    (2)约束

        如果泛型类调用泛型类型上的方法,就必须添加约束。也即指定泛型类型必须实现什么接口等。

    例:

    Public class DocumentManager <TDocument> : IDocumentManager<TDocument>

        where TDocument: Idocument

      这里用where关键字指出泛型类型TDocument必须实现IDocument这个接口。还可指定多个where子句

    class MyClass<T1,T2>:baseClass where T1:iFoo where T2:new()

    约束类型小结:

    约束                            说明

    Where T:struct       结构约束,类型T必须是值类型(直接使用struct)

    Where T:class        类型T必须是引用类型(使用时直接使用class)

    Where T:IFoo         类型T必须实现接口IFOO(实际接口名字)

    Where T:Foo          类型T必须派生于基类Foo(实际类名字)

    Where T:new()        类型T必须有一个默认构造函数(直接使用new())

    Where T:U            类型T1派生于泛型类型T2

    注意:CLR2.0中,只能为默认构造函数定义约束!

    以上的约束可以合并一起指定。   

    例:public class Myclass<T> where T:class,new()

    注意,如果new()用作约束,它就必须是为类型指定的最后一个约束。

    提示:在C#中,where子句不能定义必须由泛型类型执行的运算符,运算符不能在接口中定义,在where子句中,只能定义基类、接口和默认构造函数(不懂?)

    (3)继承

    例:

    Public class Base<T>{}

    Public class Derived<T>:Base<T>{}

    或

    Public class Derived<T>:Base<Int>{}

    Public calss Derived:Base<string>{}

    就是基类要么和子类相同的泛型类型,要么指定一个类型。

    而且子类可以是泛型也可以非泛型。

    (4)静态成员

    泛型类的静态成员与非泛型类静态成员不同,非泛型类是类的所有实例共享一个静态字段,但是泛型类中是每个泛型类的实例独享一个静态字段!

    3.泛型接口

    也就那么回事吧,没有泛型类东西多1

    4.泛型方法

    只针对类中的某个方法(静态或非静态均可)使用泛型而不是整个类。其使用定义方式和定义泛型类很相似,可以指定默认值,约束。

    例:

    Void Swap<T><ref T x,ref T y){}

    调用时可以用两种方式

    Int i=2

    Int j=3;

    Swap<int>(ref I,ref j);

    或swap(ref I,ref j);

    第二种方式由于c#编译器会通过调用 Swap方法来获取参数的类型,所以不需要把泛型类型赋予方法调用。

    5.泛型委托

    它的定义就像泛型方法一样

    public delegate TSummary Action<TInput, TSummary>(TInput t, TSummary u);

    6.net Framework其它泛型类型

    (1)Nullable<T>

       可空类型

       Nullable<int> a;

       Int? a;

       上面两个等价的

    (2)EventHandler<TEventArgs>

    (3)ArraySegment<T>

十三、集合

    非泛型集合类基本不用了!我认为

    1.常用泛型集合

        List<T>,Queue<T>,Stack<T>,

       LinkedList<T>(链表),

       SortedList<TKey,TValue>(有序链表)

       如果需要排好序的表,可以使用这个类,这个类型按照键给元素排序,它用键作为索引,实现基于数组。虽然用到了键,可不是基于字典!

       1.1Dictionary<TKey,TValue>(字典)

       工作方式:将一个键添加到字典中,键会转换为一个散列。利用散列创建一个数字,它将索引和值关联起来。然后索引包含一个到值的链接。

       (1)键的类型

       用作字典中键的类型必须重写object类的GetHashCode(),字典的性能取决于GetHashCode()方法的实现, GetHashCode方法必须满足如下要求:

       相同的对象应总是返回相同的值

       不同的对象可以返回相同的值

       不能抛出异常

       应至少有一个实例字段

       运行得要快

       散列码值应平均分布在int可以存储的整个数字区域上

       散列码最好在对象的生存期不发生变化

        键类型还必须实现IEquality.Equals(0访求或重写object类的Equals()方法,因为不同的对象可能返回相同的散列码,所以字典是用Equals()方法来比较键的。

       一般用string类型用作键值很好,而用整数用作键值不好!

       1.2Lookup<Tkey,Telement>

       这个泛型为。NET3.5新增的,它把键映射到一个值集上。

       1.3 SortedDiectionary<TKey,TValue>

       这个泛型和SortedList<TKey,TValue>功能相似,但SortedList<TKey,TValue>实现是基于数组的链表,而SortedDiectionary<TKey,TValue>

实现为基于一个字典。

       但有不同的特性

       SortedList<TKey,TValue>使用的内存比SortedDiectionary<TKey,TValue>少

       SortedDiectionary<TKey,TValue>插入和删除速度比较快。

       1.4HashSet<T>泛型(.net 3.5新增)

       这个泛型集合类包含不重复项的无序列表。这种集合叫”集set”,这个集合基于散列值,插入元素的操作非常快。跟数学上的集合一样的,交集和并集什么的!具体方法查看MSDN!

       合并一个集合,重复值被去掉了等!

十四、反射

C#.Net 使用经验

1.     System.Diagnostics.Process.GetCurrentProcess().MinWorkingSet = new System.IntPtr(5)用这个方法来强制释放内存,很管用,很好很强大!我放在了用了webbrower控件的函数中!但是我发现它把内存中的数据放在了虚拟内存中,但是虚拟内存基本不怎么长,看来还是有释放掉的!

2.     C#可选参数没有传给它值时,它不为NULL,是一个数组,判断时用Length属性.

3.     预定义字符类可以放在普通字符类中,如:[\w],根据原来的意义进行匹配!

4.  Effective C#中说的构造函数的顺序:

1、静态变量存储设置0。
2、静态变量初始化器执行。
3、基类的静态构造函数执行。
4、静态构造函数执行。
5、实例变量存储设置0。
6、实例变量初始化器执行。(声明时初始其值)
7、恰当的基类实例构造函数执行。
8、实例构造函数执行。

但是经测试的顺序是这样的。

1、  派生类静态变量存储设置0。

2、  派生类静态变量初始化器执行。

3、  派生类静态构造函数执行。

4、  派生类实例变量存储设置0。

5、  派生类实例变量初始化器执行。

6、  基类的静态变量存储设置0

7、  基类静态变量初始化器执行

8、  基类静态构造函数执行

9、  基类实例变量存储设置0

10、      基类实例变量初始化器执行

11、      恰当的基类实例构造函数执行

12、      派生类实例构造函数执行

5. .net序列化时,如果是xml序列化,基本上可以在类上不加任何的特性,而是二进制或SOAP序列化必须在类上加Serializable特性。否则报异常。