![1369733dae302dc8e751daf49cb40c78.png](https://i-blog.csdnimg.cn/blog_migrate/691ebf64f27831d59a634662dc69d9de.png)
上一篇主要说了下CLR的一些基础,这一篇继续说下CLR的设计类型
1.所有类型都是从System.Object派生,object是一切类的基类,所有类型都显式或者隐式的继承了Object,如class A{}他就等于class A:System.Object{}
2.is和as操作符
is和as操作符都是进行类型转换的,但是他俩有着一些差别;
is不会抛出异常,如 object A=new object();bool Equal=(A is object)//返回True,bool Equal=(A is 任意具体类型,如B(他是个类型))//返回false,为null时返回fasle;如果在if中使用,如if(A is object)时,如果A为空,则一直为false,在判断时,CLR会进行两次类型检测,首先看A是否兼容这个类型,如果是,则在核实是否引用,所以在性能上,is会有一定的损耗:
as在转型时也不会抛出异常,如果不能转,那他就是null,如果用as执行同样的操作,如
object e=o as object;if(o!=null)这样CLR指挥进行一次类型校验,if只判断是否为null,速度会快于is。
3.基元类型,引用类型,值类型
基元类型,如int float等编译器直接支持的数据类型被叫做基元类型;
引用类型,引用类型是指在托管堆上分配的成员,他有四个特性
![fbdc3289f5631da396ffdba7bc228984.png](https://i-blog.csdnimg.cn/blog_migrate/3c5bd9006124c5c8f609eda4fef7c630.jpeg)
值类型,所有值类型都被称为结构或者枚举,例如System.Int32结构,,所有的结构都是从System.ValueType派生,它又派生自System.Object;
值类型的装箱和拆箱:
值类型存储在栈中,没有指针,也没有垃圾回收,反之引用类型存储在堆中,通过指针进行引用,有垃圾回收,当把一个值类型转换为引用类型时,被称为“装箱”,反之为“拆箱”;
如 int a; object b=a;//装箱 int c=(int)b;//拆箱
装箱和拆箱过多造成性能的极大消耗,比如说ArrayList的本质就是把所有对象都转换为object来进行存储的,如果存储的对象是值类型,则发生装箱操作,在遍历时(比如Add,赋值等)则会则会产生大量的拆装操作,还有内存分配,复制等,极大影响效率;
dynamic基元类型,它的本质是个object类型,编译器总会在编译时把他当作一个object,因为他的类型是不确定的,编译器无法知道它到底是不是个object,dynamic可用于局部变量,字段,参数。
4.类型和成员
类型的成员包括,常量,字段,实例构造器,类型构造器,方法操作符重载,转换操作符,属性,事件,类型;
静态类特性:
![643a42c994e85f63d404d8c543b79002.png](https://i-blog.csdnimg.cn/blog_migrate/54fc9e8e108c5419fe0e0ca8df7cf360.jpeg)
CLR如何调用虚方法,属性,和事件:
调用虚方法:call 和callvirt
call可调用静态方法,实例方法和虚方法,但是call指令是以非虚方式调用虚方法;
callvirt可以调用实例方法和虚方法,callvirt则是以多态的方式调用虚方法;
常量:即指值不发生变化,如 const int a=5;,一旦确定,无法更改,且用const时必须赋值;readonly则可以不赋值,他的赋值可以延迟到构造函数里;
字段,即包含了一个值类型的的实例或者引用类型的引用, 如 public readonly int a=4;
实例构造器,即构造方法,1.当初始化一个类(引用类型)时,默认会调用一个无参的构造方法(隐式)或者自己写有参的等等,当用new关键字时,不但意味着内存的分配等,还意味着你调用了这个类的构造函数;2.当初始化一个值类型时,CLR不会主动调用构造函数,值类型默认会初始化为0,或者null,但是CLR允许为值类型定义构造函数,但必须显式定义,且用new初始化,否则不会执行(还会初始化为0或null),如
public struct Point
{ public int a;
public point(int a)
{ this.a=a;
}
}
sealed class exmple
{
public Point P=new Point(1);//会调用显式的构造函数,所以P.a=1;
public Point P2;//不会调用,直接初始化为0,所以,p2.a=0;
}
//接着昨天的写
扩展方法,当遇到有些没有提供且你想补充的时候,就可以用这个,比如你想用A类,但又不想改源代码,就可以自己写个静态类,类里面提供对这个类的扩展方法,如下:
public static class Ex
{
//this后面写的就是你要扩展的类,我要扩展A类,所以就是A
public static int ExFunc(this A,string Value)
{
//干一些你想要的
return 1;
}
}
public class example
{
void start
{
int A=Ex.ExFunc(A a , "")
Debug.log(A);//这里的A就是在那个扩展方法里面返回的1
}
}
需要注意的是扩展方法必须在静态类中声明,且不支持扩展属性,扩展事件,扩展操作符,且该类必须是顶级静态类。
分布方法略;
5.参数
传值方式和传引用方式
传值就是单纯的对于实参的复制,在方法内部对参数进行更改不会影响到外部的值如:
public int value=3;
Debug.log(value);//value=3
example(value);
Debug.log(value);//value=3
public void example(int a)
{
a=10;
}
引用方式ref和out,这个传递的则是参数的指针,会直接指向实参地址,在方法内更改参数,会影响到外部的值如:
public int value=3;//ref方式如不赋值会报错,out则无所谓
Debug.log(value);//value=3
example( ref value);
Debug.log(value);//value=10
public void example( ref int a)
{
a=10;//out在方法离开前必须赋值,否则报错
}
out和ref的区别就是,ref参数需要在用时赋值,out必须在方法离开前赋值;但实际CLR并不关心你是用的ref还是out,他俩生成的IL代码是一致的;
可变参数params;它允许你在参数前定义可变的参数如
public void Add(params int[] values)
{
}
6.属性
属性分为无参属性和有参属性(索引器);
属性的意义在于很好的封装数据,如
public int age;
这样写我在另一个类中调用时,它可以被任意赋值,比如200,这显示是不对的;CLR提供了get,set方式来获取一个字段的值,可以避免这个问题如:
public int age
{
get{return age;}
set{age=vaule;}//vaule总是代表新值,如需约束,直接if判断后在赋值即可,这样就避免了age=200或者负数的问题;
}
而且C#还提供了自动属性的语法糖,即 public int {get;set;}//无参属性
但要注意,无论是自动属性,还是显式实现属性的本质都是方法。
有参属性,即索引器如:
public class example
{
public int[]values=new int[20];
public int this[int index]
{
get{....retun 1;}
set{....return 2;}
}
}
public class Ex
{
int a=example[2];//get方法
example[2]=25;//set方法
}
索引器可以重载;
7.事件
事件基于委托,可以说事件是对委托的封装,事件的核心是回调,比如Task创建了一个事件,B和C都关注着A这个事(订阅),当A的事件发生时,将会通知B和C(回调);
如下图:
![9257efd1b125a6e299967cf463045b2a.png](https://i-blog.csdnimg.cn/blog_migrate/470288d2bf62f8bcb58ee017dfa99e43.jpeg)
![fb754668a4666a31d0cf64239c7ffee9.png](https://i-blog.csdnimg.cn/blog_migrate/862e2434845eef7dd7f4d6cff33bb45a.jpeg)
![98a3b8e66acf7d18ed33d043e9803eae.png](https://i-blog.csdnimg.cn/blog_migrate/ad3d71cbae6e42eadb46486c5c00f29d.jpeg)
![68d14727b0b04ef822692a7d3b0c3e07.png](https://i-blog.csdnimg.cn/blog_migrate/86a85723176152a71b86f3bba5bcad26.jpeg)
至于基于.net的标准事件写法,我不过多赘述,就是下面这种
public delegate void Dothings(object o,EventArgs e);
这种写法第一个参数object就是事件订阅者自身,EventArgs,就是自身的信息,可以自己写个类继承EventArgs类,然后传递一些有用的信息巴拉巴拉。。。
ClR在编译器内部实现了Add和Remove的方式,来实现上层的+=和-=
![baa17948a221950233c3f614e6ac7910.png](https://i-blog.csdnimg.cn/blog_migrate/b2386383decaf98e09294bf440a477ce.jpeg)
8.泛型<T>
泛型具有清晰,类型安全,更好性能等优势,如当你使用ArrayList时,会发生装箱拆箱,List则不会,List就是个泛型,至于什么是泛型我不会赘述,我直接说一些特性
逆变与协变
![fd5a3b4402bf90c262b1d79eb438fdec.png](https://i-blog.csdnimg.cn/blog_migrate/d6d278155686e9f8170b84b35d6e3b36.jpeg)
泛型约束where
可以对泛型进行约束 如
public class Example<T> where T:class//表示是泛型类,泛型约束为引用类型
{
}
实际上泛型的小点很多,都说的话未必能说清楚,所以其他略。。
9.接口
C#不允许多重继承,但是接口间接的实现了这个功能,可以说是另类的多重继承;
接口用Interface为关键字,一般自己定义要用大写“I”作为开头,表明这是一个接口,就像泛型用“T”一样;继承接口就必须要实现接口方法,否则接口便没有了意义。如
public Interface Imfuncation
{
void func();//定义了一个无参无返回值的方法,接口方法不允许显式标记访问修饰符
}
public class example,Imfuncation//继承接口
{
public void func()//实现接口方法,且必须为public
{
}
}
接口继承还是类继承?即看你的需求是can-do“他能做”,还是is-a“属于”
接口的小点很多我也没有细说,也是因为边幅太长(懒),所以略
下章将讨论CLR的基本类型,还是一样,文章仅是个人粗略见解,仅供参考,如有错误请指出。