C#基础加强

时间:2021-12-21 16:34:37

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  将对象备份(深拷贝。)