1、代码规范
-命名规范:定义的变量或者方法名要有意义。
1、骆驼命名 :第一个单词首字母小写,之后的单词首字母大写 userName、userPassword、realName…(普通变量(局部变量)、字段)
2、帕斯卡命名:第一个单词首字母大写,之后的单词首字母大写 GetMax()、ChangeName()…(方法名、类名、属性名)
注释:(代码说明书)
1、单行注释(//注释内容)
2、多行注释(/*注释内容*/)
3、文档注释(///注释内容)
2、.net程序基本编写、执行流程
1、编写c#代码,保存为.cs文件。
2、通过csc.exe程序来将.cs文件编译为.net程序集(.exe或.dll)。此时的exe或dll并不是机器码(cpu不可理解)。>csc /out:c:\a.exe c:\program.cs
Csc的位置:C:\WINDOWS\Microsoft.NET\Framework
设置环境变量:path后面设置。
3、程序运行时通过JIT编译(Just In Time)即时编译,将程序集编译为cpu能理解的机器码,这时cpu才能执行。ngen.exe
3、面向对象(OO)
面向对象三大特征:继承、封装、多态。
构造函数:方法名和类命相同,没有返回值,可以重载,一般为Public(private不能初始化)。
继承:(类与类之间的关系)
l Base Class→基类、Parent Class→父类
l Derived Class→派生类、Child Class→子类
里氏替换原则:需要父类的地方,用子类替换。
1、构造函数不能被继承;
2、子类对象被创建时,先会调用父类中的构造函数(默认情况会调用父类的无参构造函数);可以在子类构造函数后面加:base(……)指定要调用父类中的哪个构造函数。
this:
1)作为当前类的对象,this.成员名(调用成员,自己);
2)调用本类的其他构造函数。:this()(调用构造函数,自己)。
base:
1) 调用父类中的成员(当子类重写或者用new隐藏了父类成员时,调用父类成员);
2)调用父类构造函数。
封装
1、属性封装了字段
2、方法的多个参数封装成了一个对象
3、将一堆代码封装到了一个方法中
4、将一些功能封装到了几个类中
5、将一些具有相同功能的代码封装到了一个程序集中(dll、exe),并且对外提供统一的访问接口。(属性名、方法名等。)
1、访问修饰符
1、Private:(类中成员默认)只能在类内部访问
2、Protected:当前类和子类可以访问。
3、Internal:当前程序集内部可以访问。
4、Protected internal:当前程序集或子类中。
5、Public:任何地方。
2、访问级别约束:
1、子类的访问级别不能比父类的高。
2、类中属性或字段的访问级别不能比所对应的类型访问级别高。
3、方法的访问级别不能比方法的参数和返回值的访问级别高。
3、虚方法
1、父类中将某个方法标记为virtual,子类中可以通过override重写父类中的虚方法。
2、子类重写父类方法时,必须与父类保持一致的方法签名与返回值类型。
(“方法签名”:方法名称+参数列表,不包含返回值类型)
3、注意:父类有方法需要让子类重写,则可以将该方法标记为virtual;
虚方法在父类中必须有实现,哪怕是空实现;
虚方法子类可以重写(override),也可以不重写。
虚方法和抽象方法的区别:
虚方法 |
抽象方法 |
|
实现 |
必须有 |
必须没有 |
声明 |
可以出现在抽象类中 |
必须声明在抽象类中 |
重写 |
可以被重写 |
必须在子类中重写 |
实现多态的主要手段:
虚方法virtual、抽象方法abstract、接口。
4、静态成员、静态类
访问静态成员:类名.成员。
静态成员属于类,是大家共享的数据,静态成员直到程序退出才会释放内存。
静态类是可以有构造函数的:不能有参数、不能重载、不能有访问修饰符、必须使用static、不能显示的调用、系统在第一次使用静态类时调用一次,静态类不能被继承。
静态类的本质abstract+sealed。
静态成员只能访问静态成员。如果要访问实例成员,必须先创建对象。
什么时候使用静态成员、静态类?
在实例类中使用静态成员:当多个对象共享同一个数据的时候就可以在实例类中加入静态成员。
使用静态类:在项目中需要频繁用到的一些工具类,例如:Math、Console、Convert
5、密封类(sealed)、抽象类(abstract)
密封类不能被继承。
抽象类的作用:就是为了让子类继承。
抽象类中的抽象成员不能使用private访问修饰符,可以使用其他访问修饰符。
抽象类的特点:
1、需要abstract标记;
2、抽象方法不能有任何方法实现;
3、子类必须将抽象成员实现;
4、不能被实例化;
5、抽象类中可以包括抽象成员,可以包括有具体代码的成员(抽象类中可以有普通成员);
6、抽象成员必须包含在抽象类中;
7、抽象方法不能用static修饰。
6、多态:为了程序的可扩展性。
开放封闭原则(对修改封闭,对扩展开放);
多态就是指不同对象收到相同的消息时,会产生不同行为,同一个类在不同的场合下表现出不同的行为特征;
多态的作用:把不同子类当作父类来看,可以屏蔽不同子类对象之间的差异,写通用的代码,做出通用的编程,以适应需求的不断变化。
7、类型转化
子类—>父类:隐式转换 父类—>子类:显示转换
as 转换:成功就返回,失败就返回null,不报错。 is判断类型。
父类引用指向子类对象Person p=new Chinese();(隐式类型转换)
父类对象不能够替换子类Chinese c=(Chinese)new Person();(×)
if(obj is 类型A)//obj是父类类型对象,”类型A”是子类类型。
//显示转换 例:if(p is Chinese ) { Chinese c=p as Chinese; } |
8、抽象方法
1、抽象方法要用abstract;
2、不能有任何方法实现;
3、抽象方法必须定义在抽象类中;
4、子类继承抽象类后,必须实现抽象成员;
5、抽象类不能实例化;
6、抽象类中可以有实例成员,也可以有抽象成员;
7、抽象类中的抽象成员不能private。
何时使用抽象类、何时使用虚方法?
看这个类将来是否需要实例化,需要实例化则不能abstract;
看这个方法是否有默认实现。
new关键字:隐藏父类的方法。
1、设计模式(GOF23种设计模式)
简单工厂设计模式(案例:面向对象计算器)
各种设计模式的本质都是:多态。充分理解了多态,再看设计模式就会觉得轻松很多。
- 面向对象计算器:将参与运算的两个数和运算符抽象为一个类(Operation)、每种运算写一个类继承Operation,写一个GetResult方法,新建一个工厂类根据不同的运算符创建不同的对象返回。
- 值类型:均隐式派生system.ValueType;
2、值类型、引用类型,值传递、引用传递
数值类型、bool、结构、枚举;
赋值会拷贝一个副本。
值类型不能赋null值,可空类型的功能是让null可以赋给值类型;
值类型都有一个隐式的默认构造函数来初始化值类型的默认值。
- 引用类型:均派生自System.Object;
字符串、数组、类、接口等
赋值只复制对对象的引用。
值类型、引用类型(快捷方式)作为参数传递:
- 值传递:传递的是栈中的内容;
- 引用传递:传递的是栈中的地址。
Person p = new Person(); p.Name = "奥特曼"; M1(ref p); Console.WriteLine(p.Name); static void M1(ref Person per) { per.Name = "葫芦娃¤"; per = new Person(); per.Name = "葫芦娃¤"; } |
3、枚举(enum)
1、枚举
- 枚举对应的都是一个常量值,所以编译的时候就会替换成对应的数值。
- 将枚举转换为数值:(int)MyColor.Red;
- 将枚举转换为字符串:MyColor.Red.ToString()
- 将字符串转换为枚举:MyColor mc = (MyColor)Enum.Parse(typeof(MyColor), "Red");
2、标志枚举(flag)
当可以具有多个状态时,普通枚举就不够用了(状态可以组合)
在定义枚举前面加入[Flag]
[Flags] public enum GFBF { 高= 1, 富= 2, 帅= 4, 白= 8, 美= 16 } Person p1 = new Person(); p1.Name = "西西"; p1.CurrentState = GFBF.高| GFBF.富| GFBF.帅; if ((p1.CurrentState & GFBF.高) == GFBF.高?) Console.WriteLine("有"); else Console.WriteLine("没有"); Console.WriteLine(p1.CurrentState.ToString()); 输出结果为: |
4、结构体(struct )*
- 一种值类型,不具有面向对象的特征。
- System.Drawing.Point、Size、Color等
- 结构可以实现接口,但不能继承。
结构和类的区别:
1、结构是不可继承的;而类可以继承。
2、结构不需要构造函数;而类需要。
3、结构使用堆栈分配,类使用堆分配。
4、结构元素不可声明为 Protected;类成员可以。
5、结构从 System..::.ValueType 类隐式继承,不能从任何其他类型继承;而类可以从 System..::.ValueType 以外的其他任何类继承。
5、类型转换
隐式类型转换
1、子类类型赋值给父类类型;
2、将占用字节数小的数据类型赋值给占用字节数大的数据类型。
例:double num=10;//sizeof(double)/sizeof(int)
显式类型转换(有可能丢失精度)
例:int n=(int)num;
CAST:是在内存级别上的转换。内存中的数据没有变化,只是观看的视角不同而已。
Convert:是一个加工、改造的过程。
1、将任意类型转换成字符串:ToString()
2、将字符串转成“数值类型”(int、float、double):
XX.Parse(string str); 转换失败报异常;
XX.TryParse(string str,out int n);转换失败不报异常;
AS:转换失败返回null,不报异常。
GetType()得到该类型;GetType().BaseType得到该类的父类。
错误:
1、语法错误,编译时报错;
2、逻辑错误;
3、运行时的错误(异常);
1、异常处理:
try{//try中包含可能发生异常的代码;}
catch{//当发生异常后执行的代码,主要用来对异常进行处理}
finally{//无论是否发生异常都会执行}
- 在try中,当程序发生异常,发生异常代码后面的代码不执行,直接跳转到对应catch;
- catch中的代码执行完成后,继续执行finally中的代码。
- catch(异常类型):只捕获指定类型的异常。
- catch(异常类型 [ex]){ex.message}ex中包含很多东西。
- throw;这种写法只能在catch中使用;throw new exception()可以写在任何地方。
- throw new Exception(“错误消息”,ex);
在父类ex.innerException.mssage可以得到该原始消息。
注意:
- finally中不能写return语句;
- try中有return,finally也会执行。
- params 可变参数 无论有几个参数,必须出现在参数列表最后,可以直接传递一个对应类型的数组。
- ref 仅仅是一个地址,强制把值传递变为引用传递;
- 传递前必须先赋值。
- out 让函数可以输出多个参数;
- 在方法中必须为out参数赋值;传递之前赋值没意义;
- ref用于内部对外部的值进行改变;out则用于内部为外部赋值的,一般在函数有多个返回值的场合。
2、函数返回值(参数前的修饰符)
3、方法重载
签名相同,参数不同(个数、类型、顺序、修饰符(ref\out\param))
4、比较(Equals、==)
1、判断是否为同一个对象:判断堆栈中的地址是否一样。
2、==:(查看String)内部调用Equals实现的
3、Equals:object判断对象的地址是否相同,String判断的内容是否相同。
4、String重写了Object中的Equals方法。
5、当两个变量指向同一块内存的时候,我们就说这两个变量是同一个对象。
6、其实就是判断两个变量指向堆中的内存地址是否相同。
7、对于字符串的Equal比较的是字符串的内容,而不是对象的地址。
8、在字符串中==也是比较字符串内容。
9、字符串类中将object父类中继承下来的Equals()方法重写了(又添加了一个方法),因为运算符内部的==(运算符重载)后,内部也是调用Equals来实现的,所以结果一样。
10、在任何情况比较对象是否为同一个对象,使用ReferenceEquals。
11、Object.ReferenceEquals(s1,s2):判断两个对象是否为同同一个对象。
5、常用类库String
- 不可变性: 只要开辟的空间,就不可以修改。
- 字符串暂存池(拘留池):(针对字符串常量)
- 内部维护一个哈希表key字符串,value是地址,每次为一个新变量赋值都会找key中是否有,如果有则直接把value中的地址赋值给新变量。
- Length
- IsNullOrEmpty()
- ToCharArray()
- ToLower() 小写,必须接收返回值。(因为:字符串的不可变);
- ToUpper() 大写。
- Equals() 比较两个字符串是否相同。 忽略大小写的比较,StringComparation.
- IndexOf() 如果没有找到对应的数据,返回-1.//面试题:统计一个字符串中,”*”出现的次数。
- LastIndexOf() 如果没有找到对应的数据,返回-1
- Substring() //2个重载,截取字符串。
- Split() //分割字符串。
- Join() 静态方法
- Format () 静态方法
- Replaces ()
String字符串:
1、常用类库StringBuilder
StringBuilder高效的字符串操作,当大量进行字符串操作的时候使用。
StringBuilder!= String//将StringBuilder转换为String.用ToString();
2、垃圾回收*
CLR的一个核心功能—垃圾回收。
提高内存利用率。
垃圾回收器,只回收托管堆中的内存资源,不回收其他资源(数据库连接、文件句柄、网络端口等)。
什么样的对象才会被回收?
没有变量引用的对象。
什么时间回收?
不确定
GC.Collect();手动调用垃圾回收器,不建议使用。
垃圾回收中“代”的概念。
弱引用:WeakReference,对于创建比较耗时的对象可以使用弱引用。
用的时候应该先拿到对象,再判断是否为null;如过不先拿到可能会出现刚好判断完就被GC回收了。
3、集合
ArrayList:可变长度数组。
- 属性 Capacity(集合中可以容纳元素的个数,翻倍增长);
- Count(集合中实际存放的元素的个数。)TrimToSize;
- Add() AddRange(Icollection c) Remove() RemoveAt() Clear()
- Contains() ToArray() Sort() 排序\Reverse();//反转
- Sort()排序需要实现Icomparable接口。
一种类型需要按不同方式进行排序的时候,需要新建比较器(类)实现Icomparer接口,在调用Sort()的时候传一个比较器对象。
public class Person:Icomparable…… public class SortByName:Icomparer…… al.Sort(new SortByName()); |
Hashtable 键值对集合,类似于字典。键不能重复。
.keys遍历键、.value遍历值、DictionaryEntry遍历键值
4、泛型集合
List<T> ArrayList的升级版
Dictionary<K,V> Hashtable的升级版
遍历的时候foreach(KeyValuePaie<……> kv in dict)
1、装箱和拆箱(box、unbox)
- 装箱和拆箱的类型可以相互转换。
- 装箱、拆箱很耗时。
- 查看是否发生装箱或拆箱,Reflecter的IL
1)装箱、拆箱必须是:值类型—>引用类型 引用类型—>值类型
2)拆箱时,必须用装箱时的类型来拆箱,否则会报错
3)方法重载时,如果具有该类型的重载,那么就不叫拆箱或装箱,如console.writeLine().
4)接口与值类型之间的装箱与拆箱。(如://int n = 10;//IComparable compar = n; //装箱。)
5)显隐式转换不属于装拆箱。
2、类型推断var
- 根据“=”右边推断数据类型;
- 只能出现在局部变量中;
- 依然是强类型,在编译时就确定了类型。
- 泛型的目的:代码重用。T:一般叫做“类型参数”,把数据类型作为参数传递。一般用T类表示或者以大写T开头的。如:TKey、TResult...
- 泛型类、泛型接口、泛型方法、泛型委托。
- 泛型接口,实现该接口的类可以用指定的类型替换接口的类型参数,也可以是泛型类。
3、自定义泛型
public interface IFlyable<T> { void Fly(T msg); } |
public class MyClass2<yzk> : IFlyable<yzk> { public void Fly(yzk msg) { } } |
泛型方法,调用的时候可以指定类型参数,不指定的时候自动推断。
使用where关键字进行类型约束:
泛型约束(参数:要求)
l 接口约束:Where T: System.IComparable<T>
l 基类约束:Where T: Person
l struct/class约束:Where T: struct
l 多个约束:Where T: IComparable<T>,IFormattable
l 构造器约束:Where: new()
4、foreach (*)
1、可以被foreach遍历的要求:
- 具有GetEnumerator()方法的类型;
- 实现Ienumerable接口的类型;
- 实现Ienumerable<T>接口的类型。
一个类型只要实现了IEnumerable接口,就说这个类型可以被遍历,因为实现了IEnumerable接口,就会有一个叫做GetEnumerator()的方法,而该方法就可以返回一个可以用来遍历的“工具”。(该工具是一个自己编写的迭代器的实例,该迭代器实现了Ienumerator接口)
- 可枚举类型(具有GetEnumerator()方法)、枚举器(具有IEnumerator接口中的成员的类)
- IEnumerable实现该接口即为可枚举类型
- IEnumerator实现该接口为枚举器
//一个类型只要实现了IEnumerable接口,就说这个类型可以被遍历 //因为实现该接口后,就会有一个叫做GetEnumerator()的方法,而该方法就可以返回一个 //真正用来遍历数据的“工具对象” public class Person : IEnumerable { public string Name{ get; set; } public int Age{ get; set; } public string Email{ get; set; } string[] _name = { "赛文", "艾斯", "雷欧", "泰罗" }; //该方法有一个返回值,是实现了IEnumerator接口的对象。 public IEnumerator GetEnumerator() { return new PersonEnumerator(_name); } } |
/// <summary> /// 用来遍历Person类中数据的(迭代器) /// </summary> public class PersonEnumerator : IEnumerator { public PersonEnumerator(string[] names) { this._userData = names; } string[] _userData; //用来遍历数组元素的索引 int index = -1; public object Current { get { if (_userData.Length > 0) { //表示索引没有越界 if (index >= 0 && index < _userData.Length) { return _userData[index]; } else { throw new IndexOutOfRangeException();} } else { throw new Exception("没有数据!"); } } } public bool MoveNext() { if (index >= _userData.Length - 1) { return false; } else { index++; return true; } } public void Reset(){ index = -1; } } |
1、foreach遍历—通过实现泛型接口、yield实现
Yield返回IEnumerator或者IEnumerable (看《C#图解教程》)
被迭代的类型需要一个GetEnumerator方法返回枚举数,还需要一个迭代器(里面用的就是Yield return 简单语法)。
在使用迭代器的时候,编译器自动为我们做了很多事情,如下图:
总结:实现返回枚举数的迭代器时,必须通过实现GetEnumerator来让类可以被枚举,它返回由迭代器返回的枚举数。
对于可枚举类型,它产生的既是可枚举类型,又是枚举数的嵌套类。因此,这个类实现了枚举数和GetEnumerator方法。
2、文件操作常用相关类
- File //操作文件,静态类,对文件整体操作。拷贝、删除、剪切等。
- FileInfo//文件类,用来描述一个文件对象。获取指定目录下的所有文件时,返回一个FileInfo数组。
- Directory 操作目录(文件夹),静态类。
- DirectoryInfo //文件夹的一个“类”,用来描述一个文件夹对象(获取指定目录下的所有目录时返回一个DirectoryInfo数组。)
- Path//对文件或目录的路径进行操作(很方便)【字符串】
- Stream 文件流,抽象类。
- FileStream//文件流,MemoryStream(内存流),NetworkStream(网络流)
- StreamReader //快速读取文本文件
- StreamWriter//快速写入文本文件
- GZipStream
3、补充知识* (获取当前exe文件执行的路径)
1、Path.GetFileName()获取“C:\test\”会返回null。
2、获取当前exe文件执行的路径:
- Assembly.GetExecutingAssembly().Location;
- Application.StartupPath.
不要用:Directory.GetCurrentDirectory();获取应用程序的当前工作目录。因为这个可能会变,通过使用OpenFileDialog或者手动设置Directory.SetCurrentDirectory().
案例:资料管理器。
4、文件流
Using作用:
1、 引用命名空间;
2、 为命名空间或者类型起别名;
3、 调用Dispose()方法。
文件流操作
1、创建文件流 FileStream fs=new FileStream();
2、创建缓冲区 byte[] bytes=new byte[]();
3、读或者写 fs.Read()或者fs.Write();
1、文件流操作
FileStream的Position属性为当前文件指针位置,每写一次就要移动一下Position以备下次写到后面的位置。
文件编码
Unicode编码的前两个字节相同;
UTF-8编码的前三个字节相同。
StreamReader读取文件的方式是采用逐行读取的。
读取结束的的两种方式:
1)判断是否读取到流的末尾 read.EndOdStream方式判断
2)判断读取的行是否为null:(line=read.ReadLine())!=null
File.OpenRead()可以返回只读的FileStream.
压缩流(自学、写一个压缩工具!)
2、对象序列化
- 对象序列化是将对象(比如Person对象)转换为二进制数据(字节流),反序列化是将二进制数据还原为对象。
- 对象序列化,只能针对对象的字段进行序列化。
- 二进制序列化的时候不建议使用自动属性。
- 二进制序列化:就是把一个对象变成一个byte[],然后就可以保存或者传输。
- XML序列化(XmlSerializer):就是把一个对象变成一个XML文件形式,然后可以保存或者传输。
- JavaScript序列化(添加引用System.Web.Extension):就是把一个对象变成json格式的字符串,方便传输与保存。
二进制序列化步骤:
1、 创建一个二进制序列化器:
BinaryFormatter bf=new BinaryFormatter();
创建一个文件流
FileStream fs=new FileStream();
2、 开始序列化
bf.Serialize(fs,p1);
注意:1、要进行序列化的对象要标记为[Serializable]([SerializableAttribute]);
2、序列化的类型中的所有字段也必须标记为[Serializale];
3、被序列化的类型的所有父类都必须标记为[Serializale]。
反序列化步骤
1、创建序列化器、创建文件流
2、执行反序列化 object obj=bf.Deserialize(fs);
注意:反序列化的时候,需要被序列化类型所在的程序集,因为反序列化的时候要创建一个新的对象,该对象就是原来对象类型,创建好后根据反序列化信息赋值。
如:
序列化的应用
l 序列化的应用: ASP.Net ViewState、WCF、.Net Remoting、ASP.Net Cache 、集群等
l 将对象持久化存储,磁盘、数据库
l 将对象复制到剪贴板
l 将对象通过网络传输
l 将对象备份(深拷贝。)