对C#的认识(1)
.Net 2.0相对于.Net 1.1增加了局部类,泛型和匿名委托。
匿名委托
struct和class的区别
1. struct是值类型,class是引用类型,值类型在Stack上分配,引用类型在Heap上分配。
2. struct无法继承,class可以继承。
3. struct无默认构造器,但可添加构造器,无析构函数,无法用abstract, sealed, protected关键字(因为无法继承),可以不使用new初始化。
class有默认构造器,有析构函数,可以用abstract, sealed, protected关键字,必须用new初始化。
protected和internal的区别
protected只有在继承的子类中可以访问,但可以跨程序集(assembly)
internal只有在同一个程序集内可以访问,但可以跨类。
默认访问权限
对于类的成员,默认访问权限是private。
对于类型(类、接口、结构等),默认访问权限是internal。
对于接口的属性及方法,默认访问权限是public。
委托和事件
委托是多路广播的,即一个委托可以指向多个方法,它维护着一个方法列表,在该委托被调用时,这些方法都会被调用。
委托指向静态方法时,最好在静态方法前面加上类名(有时是必须的),在指向实例方法时,最好也加上this关键字。
事件模型是基于发布者和订阅者模式(publisher and subscribers),任何对象都能发布一组其他对象可以订阅的事件,当发布对象触发该事件时,所有订阅者都会被通知到。事件可以看作是发布它的对象的属性,关键字event用于定义一个事件,而该事件是委托的一个特殊实例,它也是一个委托对象。事件可以说是一个具有特殊标识的委托对象。具有事件标识的委托对象有一些限制,例如它只能使用+=和-=来加载或者卸载事件处理方法,它无法使用"=new MyDelegate(M1);"这样的方式来指向某个方法,也无法通过"myobject.myevent();"的方式来调用它。不过对于发布该事件的对象来说,可以通过"this.myevent();"来调用。
.Net自带的事件处理器是一个委托,一个针对事件处理的委托,它只有两个参数,并且没有返回值,一个参数是事件源,即触发该事件的对象,另一个参数是EventArgs类的对象或者是继承了EventArgs类的类的对象,用于保存一些事件参数,这些参数可以传递给事件处理器方法来进行处理。对于有自定义参数需要传送的情况,我们可以首先实现一个继承了EventArgs类的类,然后在事件处理器方法里使用该类的对象作为第二个参数。
接口和抽象类的区别
1. 抽象类可以包含实现细节和字段成员,接口不能,不过接口可以包含属性声明。
2. 类只能继承一个抽象类,但类可以实现多个接口。
3. 抽象类可以有非公共方法和属性,但接口不能(都是公共的),而且接口中的方法和属性不能使用访问权限修饰符,例如public等。
4. 抽象类可以有静态方法和变量,以及常量,但接口不能。
5. 抽象类有构造器,但接口没有。
6. 接口及其中的方法和属性不能用static, abstract, sealed和virtual关键字修饰。
接口属性的定义
interface I1
{
int InterfaceProperty{get;set;}
}
对于面向接口的编程,一般分三层:
1. 接口(定义方法规范)
2. 抽象类(实现接口,一般实现多个接口;定义数据成员及静态方法和变量,以及常量;如有需要,实现部分功能)
3. 具体类(继承抽象类,根据需求实现全部功能)
泛型接口和泛型类
对于泛型接口,它可以继承一般的接口,但如果是一般的接口继承或者是类实现它时,必须提供一个具体的类型,包括里面的方法参数和返回值以及数据成员,必须提供一个具体的类型。而如果是泛型接口继承或者是泛型类实现它时,则没有这个限制。
class MyClass<U, T> : I1<U>, I2<T>
{
I1<T> i;
public void MyMethod(I1<T> p)
{
}
}
可以通过编译。
不过,
class MyClass<U, T> : I1<U>, I2<T>
{
public void InterfaceMethod(I1<T> p)
{
}
}
无法通过编译。因为InterfaceMethod的参数类型必须是I1<U>(注:InterfaceMethod是接口I1<U>定义的一个方法)。
class MyClass<U, T> : I1<U>, I1<T>
{
I1<T> i;
public void MyMethod(I1<T> p)
{
}
}
无法通过编译。这是由于违反了接口实现的独特性,实现了两次相同的泛型接口。
不过,
class MyClass : I1<int>, I1<long>
{
I1<int> i;
public void MyMethod(I1<int> p)
{
}
}
可以通过编译,因为I1<int>和I1<long>是两个不同的接口。
当类实现多个接口时,需要对不同接口的方法一一实现,不过如果两个接口中的方法头一模一样,则只需实现一次该方法即可,如果想对两个接口的该方法都实现,那么其中至少有一个方法必须显示实现,如"void I1.Method1()",并且该方法无法使用访问权限修饰符,即为private的访问权限(默认权限),另一个如果没有显示实现,则可以设置权限。对于已提供具体类型的泛型接口实现,如"class MyClass : I1<int>, I1<long>",也需要显示实现其中一个,当然显示实现两个也是可以的。
as操作符
as操作符用于安全地进行显示转换。使用as操作符进行显示转换时,如果转换失败,将赋予变量null值,而不是抛出异常,然后可以通过判断变量是否为null来确定转换是否成功,从而可以不使用try---catch来捕获异常。
String和string的区别
String是.Net Framework中的类而string是C#中的关键字,string映射为String,就是编译的时候编译器会把它变成String,直接写String可以让编译器少做一点点工作。
Array和ArrayList的区别
Array是个抽象类,ArrayList是个具体类,Array其实就是数组,ArrayList是个集合。
部分类的好处
1、对于代码太多的类,使用部分类可以使条理更加清晰,同时便于多个程序员同时修改一个类。
2、对于WinForm和WebForm可以使控件定义及处理逻辑分开,便于维护管理。
使用部分类的注意点
1、所有该类的部分类必须使用partial关键字,并且位于相同的命名空间下。
2、每个定义的partial类必须在访问修饰符上保持一致,如果有一个部分是抽象的或者密封的或者继承自某个类,则整个类都是这样的,各个部分之间不能矛盾,所有部分只能继承自同一个类。
3、该类实现的接口为所有部分类实现的接口的总和,即具有累加效应,又比如对于属性也是一样。
4、在某一部分类定义的字段和对象可在其他部分类中使用。
5、所有部分类必须同时被编译。
6、只适用于class,struct,interface。
Hashtable和Dictionary的区别
1、Hashtable对于值类型的存储必须装箱,获取值类型必须拆箱,效率较低,且存入的数据在遍历时是无序的,但是对于多线程的话,它允许单线程写入,多线程读取,调用Synchronized方法可以获得线程安全的类型,因而多线程使用Hashtable比较方便。
2、Dictionary由于有泛型功能,所以值类型存储不用装箱和拆箱,在没有删除过数据的情况下,遍历是有序的,根据插入数据的顺序,但是对于多线程,它不是线程安全的,必须人为用lock语句保护,因而单线程用Dictionary比较好。