知识点
走进了这个世界,但不能走的很远!
第十一章
第十二章
第一章:
1、.net Framework是什么?
是一个平台,可以在这个平台上进行 开发、部署和执行应用程序。
2、C#和.net什么关系?
C#生成的代码是在.net环境中运行的,但是C#不是.net的一部分。
3、C#生成的可执行程序特点和过程和C++生成程序的区别?
从文件中的源代码到可执行的代码,两个过程:
A:源代码编译成Microsoft中间语言(IL)
B:CLR把中间语言(IL)编译成在平台(.net平台)上可以运行的代码,这各C++生成的可执行二进制代码(本地机器吗)有所不同
4、CLR
公共语言运行库,或者叫.net运行库。在CLR控制下运行的代码叫托管代码。是.net Framework的核心。CLR会检查哪些引用变量不再会访问了,然后会处理一下数据回收处理。
第二章:
1、为什么进行变量初始化?
为了安全性考虑。
2、初始化的两种情况?
方法外的变量在没有显式初始化时,编译器会自动初始化。
方法内的变量必须在使用前初始化,否则会出现错误,编译器不会自动初始化。例如:
int i;//
Console.WriteLine(i);//会发生错误: 使用了未赋值的局部变量“i”
3、类型推断?
用var 声明变量时,变量类型会随着初始化时的值来推断出变量的类型,并且一旦初始化后此变量的类型也就推断出来并固定了。
4、局部变量冲突
在类级别和方法级别(暂且这样叫)声明的同名变量,不会引起冲突。
class Program
{
private int i = 0;//类型级别
static void Main(string[] args)
{
int i = 4;//方法级别
Console.WriteLine(i);//输出4
}
}
5、常量:
const int i=9;
#:常量是静态的,不用也不能用static来修饰;
class Program
{
public const int i = 9;//类型级别
static void Main(string[] args)
{
Console.WriteLine(DDX.DD);//用类名来引用,说明是静态的
Console.Read();
}
}
class DDX
{
public const int DD = 90;
}
#:常量一定在声明时初始化,下面是错误的
const int i;
i=9;
#:常量 ,优点是能把数字表示成一个有意思的字符串 比如: const int RATE=0.80;是一个利率
6、C#基本数据类型
15种基本类型,其中有13种值类型,2种是引用类型。
7、小技巧,Char类型字符的表示,比如'A'
(Char)65 '\u0041' '\x0041'
8、一个特殊的引用string类型
string str1 = "hello a";
string str2 = str1;
str2 += "b";
Console.WriteLine(str1);//出现hello a
Console.WriteLine(str2);//出现hello ab
按正常的引用来思考:str1和str2是引用的同一个内存区域,str2的变量也会是str1的变化,但是str2变化后str1却没有变化,这是因为:修改一个字符串,就是再创建了一个全新的字符串对象。这是运算符重载的结果。
9、@在字符串中的使用
string s1="C:\\1.txt";
string s2=@"C:\1.txt";
@的作用就是把字符还当成原来的含义,比如“\”就是一个普通斜线,但是@不能作用于双引号"。
10、 switch case 语句注意点:
case 后的表达式一定要是常量;
case 后的语句为空时,就会接着执行下面的case语句,这样可以合并几个case
switch(i)
{
case 0:
case 1:
case 2:
Console.Write(i);//当是0,1,2时都会成立
break;
default :
Do Something;
}
case 后一定要有 break;否则会出错,这是和C++不同的地方。
case 中的两个或者常量不能有重复
switch(i)
{
case 1:
case 1:这样会出错的
}
11、 goto 语句的限制
不能跑到循环内部;不能跳出类。
12、枚举类型
作用:存数据,一次能存在两种类型,并且可视化强,易懂
class Program
{
public const int i = 9;//类型级别
enum TT : int
{
Monday = 9,
Tuesday,
Thirday
}
static void Main(string[] args)
{
Console.WriteLine(Convert.ToInt32(TT.Monday));//9
Console.WriteLine(TT.Monday.ToString());//Monday
Console.Read();
}
}
13、Main方法参数的使用 int Main(string[]args)
在命令执行程序时,会使用的到
int Main(string[] args)
{
for(int i=0;i<args.length;i++)
{
Console.WriteLine(args[i]);
}
Console.Read();
}
第二章部分.exe 23 34 eee bbb
输出如下:
23
34
eee
bbb
14、格式化输出字符
double a = 0.123;
Console.WriteLine("{0:##.#0}", a);//输出.123
Console.WriteLine("{0:##.#000}", a);//输出.1230,说明当0处有字符时,会用字符来代替掉0,但是如果0处没有字符时,还会显示0;说明当#处有字符时,也会被字符替换掉,但是当#处没有字符时,就不显示#;这是#和0当占位符用时的区别
15、三个斜线注释的作用
可以直接对一个方法或者类进行说明;设置VS--项目--属性--生成 选项卡里 选中生成XML文件;这样会在执行项目时生成XML格式的说明文件。
T "表 示一个类型,F :"表示一个字段,"M∶ "表示一个成员
16、预处理器指令
作用:可以让一部分代码不会编译。这样,想生成一个程序的测试版本或者完全版本就有了用处。调试时也会用到。
#define #undef #if #endif #elif #else 可以进行调试作用,
#pragma 可以消除一些警告 例如字段定义后未使用会在编译时给出警告 这个警告的代号是169 ,#pragma warning disable 169 这样就消除了这种类型的警告;在发出警告或者错误提示时会在IDE中有行号显示,使用 #line 可以改变行号 比如:在第100行时出现错误 if(i=0),默认情况下系统会提示在100行的错误,但是在100行后使用 #line 101 "Class1.cs" 这时就会在错误时提示是在101行出现错误,如果想恢复原来的默认行号,可以使用#line default
#warning "这里会出现一个警告" 会在出现#warning 处出现一个警告,警告的内容是后面的字符;
#error "提示一个错误" 会在这里出现一个错误提示,编译不会再执行下去,停止了编译器的执行
#region 与 #endregion 只是起到代码源文件中模块化的作用。
17、一些标识符的规则,小技巧与常识
一般情况下C#的关键字不用作标识符的,例如int string 等,但是加上@时可以用作标识符了,@string
string @int = "sss";
Console.WriteLine(@int);//可以正常使用
标识符也可以包含Unicode字符,比如\u005f表示"_"下划线,_aa与\u005faa都可以标识。
第三章:
1、类成员的4种访问类型?
internal public protected private
2、类的常识:类的两个成员
函数成员和数据成员;
函数成员:方法、构造函数、析构函数、属性
数据成员:字段,常量
3、引用传递的好处
按 引用传递的 效率更高,不用复制更多的副本,也就是少占据内存空间
4、数组作为参数时是一种引用传递
5、强迫值传递为引用传递, ref 和 out 关键字的使用,两者的区别?
6、命名参数怎么用?可选参数的注意点?方法重载的几点要求?
7、属性Get和Set代码段在什么时间会调用到?构造只读和只写属性怎么做?
访问属性其实是间接与字段交互数据,这个字段是和对应的属性关联的。
Age = 5;//其实执行的是age = value(5)+2;结果是7
Console.WriteLine(age);
int i = 0;
i = age;//其实执行的是get里面的代码 i=age(0);结果是0,和想象的结果1不一样
Console.WriteLine(i);
private int age = 0;
public int Age
{
get
{
return age + 1;
}
set
{
age = value+2;
}
}
8、属性的自动实现
如果属性的 set和 get访 问 器中 没有任何逻辑,就可以 使 用 自 动实现的 属性。
public int Age{get;set;}不需要声明 private int age。 编译器会 自 动创建它。
如果把属性名Age变成AGE或者aGE等呢?还没有尝试,在VS2005中会出错。
9、静态构造函数
与普通非静态构造函数不冲突,一个类中可以同时存在这两种构造函数。
在什么时间执行?由.net库来执行,并且只最多执行一次,不确定的某个时间执行,可以确定的是在类引用前就会执行了。
使用的原因?一些类中有一些静态字段,不能用普通的构造函数来初始化,所以要用静态的构造函数来初始化。
10、构造函数间的调用和构造函数初始化器
class A
{
public A():this("BBB") //先执行有一个参数的构造函数,再执行第一个构造函数本身内的代码
{
Console.WriteLine("A");
}
public A(string s)
{
Console.WriteLine(s);
}
}
11、只读字段与关键字readonly
与 const 常量的区别:readonly是在执行时才知道其值, const 是在编译时就已经有了值;readonly可以赋值,但是只在构造函数中赋值一次, const 常量在定义时就要初始化。
12、结构
结构同样有构造函数,但是没有析构函数,是因为结构是值类型,初始化结构时可以像类那样用new关键字,但是也可以不用new,虽然使用了new,但是不会在堆上分配内存。
结构的自定义的构造函数不能没有参数。
在结构的内部不能直接为字段赋值。
struct S
{
public S(int i)
{
this.i=i;
}
public int i=0;//这是操作是错误的,结构内部不能直接赋值给字段
}
13、类的拆分和关键字 partial
在 class interface struct 等前面加上 partial关键字,在编译时编译器认为在不同文件中的同名 类/结构/接口 合并。
文件1.cs中:
partical class A
{
public int i=0;
}
文件2.cs中:
partical class A
{
public int j=9;
}
其实两个类是一个类。这在WinForm程序中容易见到。
14、静态类
类关键字 class 前面 static ;
不用再在类中的成员前面加关键字 static 了,所有成员都是静态的了。
第四章:
1、实现继承和接口继承:
实现继承:一个派生类继承自基类;接口继承:派生类继承自接口。
不支持多个实现继承,但是可以多个接口继承。
结构的继承,只有接口继承。
2、虚方法和虚属性
除了构造函数和静态函数外,其他方法和属性都能声音为虚的,用关键字 virtual 来修饰。虚方法和属性可以在派生类中重写;
重写时要用关键字 override 来修饰,C++中不需要使用 override 。
3、隐藏方法
基类中有方法fun();派生类中同样有相同签名的方法fun();那么在派生类中声明方法fun()时要使用 new :
public new int fun();表示显式的说明,隐藏了基类的方法,如果不使用new的话,编译时会出现一个警告。隐藏方法没看出来会有什么实际意义 的作用。
4、调用函数的基类版本。
class father
{
public virtual void fun()
{
Console.WriteLine("father");
}
}
class child:father
{
public override void fun()
{
Console.WriteLine("child");
base.fun();//在派生类中重写基类的虚方法,如果想调用基类中的虚方法,可以使用base.[方法名]
}
}
5、抽象函数和抽象类;
抽象函数被 abstract 修饰,抽象函数是虚拟的,但不能用 virtual 修饰。
抽象函数不能有代码,只能被子类中实现。
如果函数被抽象了,也就是成为抽象函数,所在的类也必须也要被 abstract 修饰。此时类就成了抽象类。
抽象函数 有点像是C++中的纯虚函数。
抽象类中可以有普通函数。
abstract class father
{
public abstract void fun();//只能在派生类中才有实现代码
}
6、密封函数和密封类:
被 sealed 修饰的方法或者属性不能被派生类继承;被 sealed 修饰的类,表示类不能被继承。
7、派生类和基类构造函数的调用顺序。
class C
{
public C(string s)
{
Console.WriteLine(s);
}
}
class D:C
{
public D():base("ccccc")//派生类的构造函数在被调用时,基类的构造函数会先被调用,调用方法是在派生类的构造函数后,使用base(实参数列表)
{
Console.WriteLine("D");
}
}
8、继承时 private 修饰的数据成员和函数成员,不会在派生类中看到。
class C
{
private string s = "abc";
protected string s2 = "aaa";
public string s3 = "bbb";
public C(string s)
{
Console.WriteLine(s);
}
}
class D:C
{
public D():base("ccccc")
{
Console.WriteLine("D");
}
public void fun()
{
Console.WriteLine(s);//出现错误,基类中private修饰的数据不会在派生类中被看到,protected public 可以被看到
}
}
9、接口相关
接口中不存在构造函数和字段。
接口中的方法不能有实现代码。实现代码在派生类中,并且一定要实现。
接口不能实例化。
接口中的成员(方法)不能有修饰符 public private protected 等。因为里面的方法一定都是public类型的。
接口之间可以继承。
10、接口引用类。
interface IFather
{
}
class Child : IFather
{
}
Main()
{
IFather [] arr=new IFather[2];
arr[0]=new Child();
arr[1]=new Child();
}
第五章:
1、泛型类,泛型接口,泛型结构,泛型方法
class teacher<T>
{
public T Name{ get;set};
public void PrintName()
{
Console.WriteLine(name);
}
}
2、默认值 default
class Teacher<T>
{
public void Print()
{
T t = default(T);//函数级别的变量需要显式初始化,但是事先不知道T是值类型还是引用类型,所以要用default关键字来处理初始化。
Console.WriteLine(t);
}
}
3、约束
class A
{
}
class Teacher<T> where T:A //where中,约束格式,说明泛型类型T是类类型并且继承自A类,多个约束间用","隔开
{
}
几种固定的约束:
where T:struct //约束T为值类型
where T:class //约束T一定是引用类型
where T:IF //约束T继承接口IF
where T:new() //约束T要有一个默认的构造函数
4、泛型类的静态成员
class Teacher<T>
{
public static int x;
}
Teacher<string>.x=9;
Teacher<int>.x=90;
Console.WriteLine(Teacher<string>.x);//结果是9,好像有多个静态成员
5、协变与抗变(向上转型)
class Base
{
}
class child:Base
{
}
Base b = new Child();//正常
Child c = new Base();//不能通过编译
基类的变量(b),可以引用子类的实例,因为所有的子类的都是基类的一种
但是,子类的对象不能引用父类的实例
6、泛型方法的定义和使用
void swap<T>(ref T x,ref T y)
{
x=x+y;
}
调用
int i=0;int j=9;swap<int>(i,j);
7、泛型方法与约束
void swap<T>(ref T x) where T:struct
{
}
第六章:
1、数组的初始化和赋值
用一条语句初始化: int []arr = new int[3]; arr[1]=1;...
用两条语句初始化: int []arr; arr = new int [3]; arr[1]=1;...
初始化时进行赋值: int []arr = new int[3] {1,2,3};或者 int []arr = new int []{1,2,3};
使用花括号初始化: int []arr = {1,2,3};
注意:不论是在函数内还是在函数外,数组是引用类型,数组中的元素在没有显式赋值时,都是有初始的值,比如int类型数组,元素初始是0;
2、锯齿数组
int [][] arr = new int[3][];//3行数组,但是每一行的元素的个数还不确定
arr[0] = new int[2]{1,2};//第一行有2 个元素
arr[1] = new int[6];//第二行有6个元素
arr[2] = new int[3];//第三行有3个元素
锯齿数组的初始化一定要分两步进行!
3、Array类
是所有数组的基类;是一个抽像类。
Array arr = Array.CreateInstance(typeof(int),3);
int [] a = (int [])arr;
4、数组的复制
int [] a =new int[3];
int[] b = new int[2] { 1,2};
a = b;//这里的a是对b的引用,
Console.WriteLine(b[1]);
Console.WriteLine(a[1]);
a[1] = 9;
Console.WriteLine(b[1]);//和以前的不一样,变化为9,引用的一个表现。
Console.WriteLine(a[3]);//出错,只有两个值
5、元组
数组中的每个元素都是相同的类型;元组中的每个元素的类型不一定相同。
6、 yield 关键字的使用:yield return ; yield break ;
预定义的集合类型中,可以使用 foreach 语句得到集合中的每个元素 例如List;
当要自定义一个能使用 foreach 集合时,或者想在一个方法中返回很多数据,并且数据在一个集合中,以供别的程序foreach遍历时,就要使用到yeild关键字。
IEnumerable GetA()
{
int i=0;
while(i<100)
{
yield return i; //使用了yield,返回 的值都会在IEnumerbale 对象中了,但是循环会接着执行。
i++;
}
}
第七章:
1、++i和i++
当占据一行时,会有相同的结果;
当在表达式中时:
int i=0;
int a= i++;//结果 a=0;
int b = ++i;//结果 b =1;
结论:当它们用于较长的 表达式的内 部时,把运算符放在前面t++x,会在计算表达式之前递增 x,换言之,递增了x后,在表达式中使用新值进行计算。而把运算符放在后mtx++9会 在计算表达式之后递增 x-使 用 x的 原始值计算表达式
2、溢出和处理 checked unchecked
溢出的处理,
byte i =255;
i++;产生的溢出,默认情况下是不会检查的,为 unchecked
如果代码块放在 checked 中
checked
(
i=255;
i++;
)//会发出一个异常
3、检测 对象或者变量是不是属于一个类型,或者属于一个类型的派生类型
int i = 8;
if(i is object)//is运算符的使用
{}
4、as运算符在类型转换中的使用
object o1 = "stringss";//引用的是String类型的对象
object o2 = new Hashmap();
string s1=o1 as string;//s1赋值为string 类型的引用
string s2=o2 as string;//s2会被赋值空null
结论:如果对象名(o1)引用的内存数据("stringss")属于某个类型(string)或者其派生类型,则可以转换;否则结果是null.
5、不安全代码中使用 sizeof
sizeof(int);得到是int占据的长度。4个字节。
6、可空类型:此值可以是基本的值,也可以是null
int? a=null;//不会出现语法错误
int? b=9;
int? c= a+9;//c=null结果
if(a>9){}//false
结论:当与空数据进行运算时,只要有一个为null,结果就是null;在比较中特殊:两个进行比较的数据,只要有一个为null,结果是false;
7、值类型和引用类型之间的转换:装箱和拆箱
装箱:CLR为值在堆上分配一个临时的内存,保存装箱后的结果。
8、运算符重载
struct A
{
public int a,b;
public static A operator +(A la,A ra)
{
A aresult;
aresult.a = la.a+ra.a;
aresult.b = la.b+ra.b;
return aresult;
}
}
注意:运算符重载定义时一定要使用 public static operator 关键字。
9、比较运算符重载时,要成对的重载,否则编译出错
例如重载>=时也要同时重载<=;重载==时也要重载!= 等。
10、自定义类型的强制类型转换
pubic static implicit operator float (CurrenCy value) {return float值;}使用时是:float f = (float)crrrency; implicit是指隐式转换,explicit是显式的
第八章:
★、委托类型可以定义在类内,类外,也可以在名称空间中。
★、类型为***的委托,如delegate void INTO(); INTO into = new INTO(**); 类型为INTO的委托into. 委托实例 的实例化方法有至少三种方法
1 delegate void PRINTF(); 2 static void Main(string[] args) 3 { 4 Program p = new Program(); 5 PRINTF printf = p.fun;//第一种实例化方法 6 //PRINTF prinf = new PRINTF(p.fun);//第二种实例化方法 7 //PRINTF prinf = delegate(){Console.WriteLine("00000")};//第三种实例化方法 8 printf();//会调用两个方法的 9 Console.Read(); 10 } 11 public void fun() 12 { 13 Console.WriteLine("一个方法"); 14 }
★、调用委托的两种方法
1 delegate void PRINTF(); 2 static void Main(string[] args) 3 { 4 Program p = new Program(); 5 PRINTF printf = new PRINTF(p.fun); 6 //委托初始化后,两种等值的调用方法 7 printf(); 8 printf.Invoke(); 9 Console.Read(); 10 } 11 public void fun() 12 { 13 Console.WriteLine("委托的调用方法"); 14 }
★、可以使用静态方法和非静态方法初始化一个委托实例
★、一个委托中包含多个方法调用,叫多播委托。
1 delegate void PRINTF(); 2 static void Main(string[] args) 3 { 4 Program p = new Program(); 5 PRINTF printf = new PRINTF(p.fun); 6 printf += p.fun2; 7 printf();//会调用两个方法的 8 Console.Read(); 9 } 10 public void fun() 11 { 12 Console.WriteLine("第一个方法");//一个问题,当这里有异常时,不会再往下调用方法了 13 } 14 public void fun2() 15 { 16 Console.WriteLine("第二个方法"); 17 }
1、自定义委托和通用的两种委托
一般使得的委托都是自定义委托,使用委托的示例:
private delegate int MultiTwo(int i);
int Func0(MultiTwo f,int b)
{
return f(b)+1;
}
int Add(int i)
{
return i+9;
}
main()
{
MultiTwo mt = new MultiTwo(Add);
Func0(mt,3);//委托可以作为函数的参数
}
通用的两种委托:Action<in T1,in T2> 此种委托有参数,但是没有返回值,T1和T2是传入的参数,这种委托的参数最多16个。
Func<in T1,in T2,out Result>,有返回值的委托,最后一个参数是返回值的意思,并不会传入,也是最多16个参数。
Func<int,int> AFun = new Func<int,int>(Add);
public void CreateInt(Func<int,int> action,int a)
{
int b = action(a);
}
2、匿名方法
Func<int i,int j> F = delegate(int a)
{
return a*2;
}
public delegate void PPint(string s);
main()
{
PPint pp = delegate(string s)
{
Console.WriteLine(s+"pp");
}
pp("dengdexin");//dengdexinpp
}
3、Lambda表达式的使用
C# 3.0以后才会有此语法,当委托作为参数时,容易使用的到
适用于匿名方法:
int string con="aaaaa";
Func<string a,string b,string c> SPintf= (参数列表,和前面对应,这里是两个 ,如果只有一个参数的暑假,括号可以省略)=>
{
方法体,如果只有一句的话,可以不用{}括号。
c=a+b;
c=c+con;//这里表达式可以使用外部的变量。一个重要特点。
return "ccc";
}
方法参数只有一个和多个的情况:只有一个时参数列表不用括号,多个时需要。
方法中只有一条语句时,不用写{}和return ,编译器会做这个,例如:
1 delegate int PRINTF(int a); 2
3 PRINTF printf = a=>a * 2;
lambda表达式使用外部的变量时:编译器做了大量的工作,先是创建匿名类,再在类中添加字段,字段就是那些被调用的外部变量,同时用外部变量的值初始化此类,得到实例,再调用类的一个方法,也就是表达式块的方法部分。
4、委托的另一个用处:事件
事件基于委托。
事件的两个基本关系者:发出者和侦听者;
1 class Sender 2 { 3 public delegate void EventHandler(object o,EventArgs e);//定义委托 4 public event EventHandler eventer;//定义事件eventer 5 public void SendEvent()//触发事件 6 { 7 eventer(this,new EventArgs()); 8 } 9 } 10 class Listener 11 { 12 public void Handler(object o,EventArgs e) 13 { 14 Console.WriteLine("我接收并处理了消息"); 15 } 16 } 17 main() 18 { 19 Sender sender = new Sender();//一、创建事件触发者 20 Listener listener = new Listener();//二、事件侦听者 21 sender.eventer+=listener.Handler;//三、注册事件 22 }
第九章:
★、System.String类型是.net 的类型,而string 才是C#语言的类型。
★、使用索引器的方式提取字符:
1 string h = "hello"; 2 char c = h[4];
1、String的一些劣势
string str="hello";//假如想把每个字符用它后面的字符表示,以示加密:h->i;e->f;l->m;o->p;
这时对每个字符的操作都会在内存中再开辟一段新的内存,以存在修改每个字符后的字符串,
第一次 iello,开了一个内存
第二次 ifllo,开了一个内存
...
效率低下。
StringBulid 不会出现这种问题,因为在使用StringBulid的对象时,第一个使用时会给定一定大小的内存,所有操作都是在这个内存中。
当对字符串中的每个字符进行操作的时候,使用SB比较好一些。
2、正则表达式。
第十章:
1、泛型类:List<T>
初始化并设定初始值:
List<int> intlist = new List<int>(){2,3};
添加元素使用Add方法:
intlist.Add(4);
一次添加多个元素,使用AddRange方法:
intlist.AddRange(5,6,7);
插入元素Insert:
intlist.Insert(2,8);
2、队列
先进先出(FIFO)
3、栈
后进先出
4、链表
优点:插入元素时会非常快,
缺点:在查找后面的元素时,会慢。
5、有序列表
SortedList<Tkey,Tvalue>
6、位数组
BitArray
每一个元素可以理解成一个Bool类型, BitArray ba = new BitArray(8);//设置长度为8,有代替多个Bool值的功效。
BitVector32 是另一个位数组类,它和BitArrary不同在于,它是固定位数的,是32位,这算是它的一个缺点,但它的优点是数据元素存在栈上,运算速度 快。
第十一章:
1、什么是LINQ?
语言集合查询,是用来访问数据的编程模型。个人的理解是有点像是SQL语句。
2、
int[] numbers = new int[] { 1, 3,5,7, 9, 11, 13, 15, 17,19};
var even = from number in numbers
where number % 3 == 0
grouprby number descending
select number;
关键字:from in where groupby select
3、实现排序,查询等操作
4、
LINQ提供了Count()、Average()、Max()、Min()、Sum()等方法实现聚合查询。
5、使用Skip()、SkipWhile()、Take()、TakeWhile()方法组合实现分区类查询
第十二章:
1、Dynamic Language Runtime
是添加到CLR的一些服务,这些服务允许添加动态语言,
2、Dynamic类型,和Var类型的比较
Var:在运行时一旦初始化类型第一次后,以后就是这一类型,类型变固定了。
Dynamic:在运行时可以随时改变表示的类型。
动态变量引用的类型随时变化:
main()
{
int i=9;
Dynamic dyn = i;
string str = "aaa";
dyn = str;//这时又发生了变化。
}
Dynamic类型不会在编译时报错(就算有错也不会报)
main()
{
Dyanmic dyn = new person();
dyn.GetA(1,2,3);//引用了三个参数,和实际的一个参数的情况不相同,但在编译时不会报错,在运行时会报异常
}
class person
{
void GetA(int i)
{
}
}
第十三章:
1、下面的作用域
{
这里面的作用域叫块作用域或者结构作用域。
}
2、托管堆
和普通的堆不同,它是在垃圾回收器的作用下工作的。
3、托管资源和非托管资源?
C#常见的非托管资源:文件旬柄、网 络连接和数据库连接;垃圾处理器不会处理非托管资源,因此需要手动实现。
使用两种方法来翻译非托管资源:
析构函数和实现 systemDisposable 接口。
4、析构函数
在垃圾回收器能处理托管资源时,当有析构函数时,会先执行析构函数。再执行垃圾回收器,第一次执行析构函数时不会清除对象,第二次会清除。
C#中CLR会维护一个线程来执行析构函数,如果析构函数很多时 ,就会明显影响程序的性能。
在C#中使用析构函数:
~MyClass(){}
;在方法体中对非托管资源进行释放。
编译器做 了许多工作,编译器会把析构函数编译成对Finalize方法的调用:
1 protected override void Finalize() 2 ( 3 try 4 ( 5 // destructor 土 mp1ementation 6 ) 7 finally 8 ( 9 base.Fina1ize(); 10 ) 11 )
5、实现Disposable接口。
接口Disposable中有一个方法Dispose,可以在此方法中显式对非托管资源的释放。
class MyClass : Disposable
{
private void Dispose()
{
//这里释放资源
}
}
有些类的一些方法比如Close已经实现对方法Dispose的调用。
using()子句也会自动调用Dispose来对非托管资源的释放。
★、析构函数和Disposable接口实现:两种释放非托管资源的方法
析构函数,调用的时间不确定。
Dispose方法,需要显式的调用。
两种基质的同时使用能确保非托管资源的释放。
★、unsafe标记的内容
1、标记方法,方法内可以使用指针等不安全代码
unsafe int Add() { //不安全代码 }
2、标记代码块,代码块内可以使用指针等不安全代码
1 int Add() 2 { 3 unsafe 4 { 5 //不安全代码 6 } 7 }
3、标记类,类内部的任何方法中可以存在不安全代码
1 unsafe class CC 2 { 3 //内部的任何方法可以存在不安全代码 4 }
4、标记字段,字段可以用指针类型
1 class CC 2 { 3 unsafe int *pInt ;//指针 4 }
6、C#中使用指针
使得指针的函数或者类要使用关键字unsafe,表示是不安全代码
unsafe int GetPoint()
{
//使用指针
int *p=null;
}
umsafe 关键字可以修饰变量和函数,还可以修饰类。但局部变量不能使用unsafe。
7、不能存在指向类的指针,因为垃圾回收器不会维护关于指针的信息。
class A
{
public long x;
public long y;
}
A a=new A();
long *pX = &(a.x);//出错
原因:垃圾回收器会把对象(托管类型的成员)放到一个新单元上。
处理方法:使用Fixed关键字,使用是告诉垃圾回收器,在Fixed块中的托管对象,这时可能会被指针用,不能移动。
fixed(A a= new A())
{
long *pX = &(a.x);
}
8、stackalloc
1 double * pDouble = stackalloc double[20]; 2 //在栈上分配20个double的内存
说明在栈上分配内存
第十四章:
1、特性
什么是特性?特性的分类?
特性:其实质是一个类,并且此类继承自 System.Attribute 类。特性是提供有关编程元素(如类型、字段、方法和属性)的附加信息的描述性标记
特性分为两种,一种是Microsoft定义好的,一种的我们自定义的。
特性是作用:举一个例子,Dllimport特性,修饰一个方法时,是为了影响编译器,告诉编译器下面的方法是从某个DLL引用来的。
★、内部特性影响编译器的工作,自定义特性不会影响编译器
★、特性在编译好的程序集中是以元数据的形式存在的,可以通过反射读取这些元数据包含的信息
2、如何自定义一个特性?
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherted=false)]
public class MySelfAttribute:Attribute
{
public MySelftAttribute(string str)//类可以有构造函数的,特性也是一种类的
{}
}
这样就定义了一个特性。定义特性的说明:
AttributeUage:这也是一个特性,是Microsoft定义好的一个特性。它为定义其他特性而生。
AttributeTargets.Property:AttributeTargets,是一个枚举类型,Property只是其中的一个元素。在这里说明自定义的特性MySelfAttribute只能修饰属性Property。AttributeTargets还有很多元素,分别表示自定义的特性可以修饰类、方法等。
AllowMultyple:是一个Bool类型,它表示自定义的属性MySelfAttribute是不是可以多次使用。为false表示只能使用一回。
Inherted:表示自定义的属性在修改一个元数据(属性或者方法或者类等)时,当元数据被继承时,特性是不是也可以继承。
3、如何使用自定义的特性?
用特性来修饰一个类:
[MySelf("aaaa")]//这里也可以用[MySelftAttribute("aaaa")]好像是使用构造函数来初始化一个实例一样。
public class A
{
}
但是修饰了一个类A后,有什么作用呢?
★、Type类 有许多派生类,和每个类型相对应;
★、得到每个类型的Type引用的三种方法
Type t=typeof() Type t=实例.GetType() Type t=Type.GetType(实例所属类的完整类名)
★、Type类的实例很重要的用处是能得到某个类的各种成员信息(详细信息),
4、Type类及其作用是什么?
Type :是一个抽象类,不能用其构造函数来初始化一个实例,只能用其他方法。
第一种:用 typeof double d = 0.0;Type t = typeof(d);
第二种:用GetType方法 double d = 0.0;Type t = d.GetType();
第三种:用Type类的静态方法 Type t = Type.GetType(d);
那么作用是什么呢?
是根据一个类型的实例,比如d,得到这个实例d所对应的类型的各种信息,这些信息包括:
实例对应类型的名字,在这里是System.Double;类型的成员有哪些,成员的名字是什么,成员的类型是方法还是字段;类型所在的命名空间等。详细见Type类。
5、特性的使用?
A:标识程序集
程序集标识,三种特性与强名称(比如a.dll)来完整标识一个程序集。引用程序集时Assembly.Load(参数可以是程序集的区域特性和版本特性),这是特性的一个用处。简单的说,用特性来描述程序集后,可以被静态方法Load利用来得到程序集的实例。
B:影响编译器,比如 [DllImport("User32.dll")] 的作用 。
6、可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。
namespace 反射
{
class Program
{
static void Main(string[] args)
{
/*******第一种反射表现:动态创建实例******/
Assembly ass = Assembly.Load("反射");//加载程序集
object o = ass.CreateInstance("反射.DD");//用完整的空间路径
(o as DD).WriteString("呵呵");
/******第二种表现:从现有对象获取类型并调用其方法*****/
Type t = ass.GetType("反射.DD");
MethodInfo mi = t.GetMethod("WriteString");
mi.Invoke(o, new object[] {"嘿嘿" });
Console.Read();
}
}
public class DD
{
public void WriteString(string para)
{
Console.WriteLine("反射动态调用方法:"+para);
}
}
}
★、特性是用来标记(类和方法等编程元素)的,反射是用来访问(特性和类等编程元素)的
第十五章:
1、结构
try
{
if()
{
throw Exception1("1");
}
if()
{
throw Exception2("2");
}
....
}
catch (Exception1 e)
{
}
catch (Exception2 e)
{
}
....
finally
{
}
2、代码没有处理异常时的情况?
当我们的代码没有处理异常时,应用程序会立即退出,并弹出一个对话框,运行程序时常见的错误。对话框中会表示出异常的详细信息。
3、嵌套的try块。
try
{
//这里的异常会在外部异常处理
try
{
内异常,这里的异常会在内层异常中优先处理,再内部finally,再外部catch 再外部finally
}
catch (内层异常)
{
}
finally
{
内层异常清理
}
}
catch (外层异常)
{
}
finally
{
外层异常清理
}
★、执行步骤:
(1)进入try块
(2)如果try中没有错误,在try中执行完成后进行入finally块(如果存在的话);如果try中有错误,进入catch块,第(3)步。
(3)在catch中处理错误,
(4)在catch中处理错误结束后, 如果存在finally块,就进入finally块。
(5)finally块中执行
问题:
1、try中存在并且可执行到return,还会进入finally吗?
2、catch中有并且可执行到return的话,还会进入finally吗?
★、有多个捕捉时,走进入最相关的catch,而不进入其他,包括兼容性的异常.
1 try 2 { 3 int zero = 0; 4 int i=10/zero; 5 } 6 catch (DivideByZeroException zeroexc) 7 { 8 Console.WriteLine("zeroexc");//这里执行 9 } 10 catch(Exception ex) 11 { 12 Console.WriteLine("exception");//这里不执行 13 }
★、内层异常没有捕捉时,把异常抛给外层处理,并且在外层处理前先执行内部异常的finally块.
1 try 2 { 3 try 4 { 5 int zero = 0; 6 int i = 10 / zero;//内部异常没有捕捉,把异常抛给外层处理 7 } 8 finally 9 { 10 Console.WriteLine("finally");//先执行 11 } 12 } 13 catch (DivideByZeroException zeroexc) 14 { 15 Console.WriteLine("zeroexc");//后执行 16 }