首页 > 技术文章 > .Net基础加强

mumuyinxin 2018-09-17 13:01 原文

1-1-3(索引器,封装,继承)

.net Reflector反编译工具。简单来说,就是把一个已有的程序转化为编程的命令行。

string str = string.Empty;//空字符串
Console.WriteLine(“总共有多少个省市{0}”,list.Count);//输出一行带有换行的数据
采用{0},{1},{2}…{n}来取代后面的想输入的n个量,像C语言中就有类似的%d一样。
Console.ReadLine();//输入数据

list.RemoveAt(list.Count-1);//除去集合中最后一个元素
name.Inodexof(‘王’)==0;//查找到”王”这个字是否在索引为0的位置上,是则为true,否则为false;

方法名,类名,大写的字母。
变量名,小写的字母
private string _name;//字段,小写的
public string Name//封装,即将方法封装成了属性,属性其实就是方法。属性本身不能存储数据,只是他后面肯定会有支持属性的字段。
{
​ get
​ {
​ return _name;
​ }
​ set
​ {
​ _name=value;
​ }
}

public string Name//可以直接这么写
{
​ get;
​ set;
}

继承:
新建一个类,
然后在public class 类名:需要继承的类名
重写:
在重写的方法名前加上virtual,
public virtual void 方法名(){}
然后在新建的类中重写的方法,
public override void 方法名(){}

索引器://是一个属性,一个非常特殊的属性,常规情况下是一个名叫Item的属性
public string this[int index]
{
​ get
​ {
​ if(index < 0 || index>=_names.Length)
​ {
​ throw new ArgumentException();//抛出错误
​ }
​ return _names[index];
​ }
​ set
​ {
​ _names[index]=value;
​ }
}

String.indexof(str)的用法
int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。
int indexOf(int ch, int fromIndex) 从指定的索引开始搜索,返回在此字符串中第一次出现指定字符处的索引。
int indexOf(String str) 返回第一次出现的指定子字符串在此字符串中的索引。
int indexOf(String str, int fromIndex) 从指定的索引处开始,返回第一次出现的指定子字符串在此字符串中的索引。
例如:
String name="Hi Mary, Mary quite contrary";
name.indexof("Mary");
C#结尾的Console.Readkey()有什么作用啊?
等待键盘输入,退出程序。使调试时能看到输出结果。如果没有此句,命令窗口会一闪而过。

1-1-4(windows窗体应用程序)

windows窗体应用程序
基本窗体的创建

public 产品分类()
{
​ InitializeComponent();
}
中的InitializeComponent();
具体作用:

InitializeComponent();
是有.NET平台自动执行的,是做一些初始化的工作,例如: 初始化FORM,上面的控件,加载资源,分配资源等

大部分加载的是 xxx.designer.cs 里的东西

1-2-1(构造函数,属性和对象,LSP里氏替换原则,构造函数问题)

构造函数
函数名和类名一模一样,没有返回值,是public,不能是private,无法访问。

在类中直接声明的变量叫做类的成员变量,类的成员变量声明以后可以不赋初值,因为会有默认值。(0,null,false之类)

属性和对象本身就是封装的体现

  1. 属性封装了字段
  2. 方法的多个参数封装成了一个对象
  3. 将一堆代码封装到了一个方法中
  4. 将一些功能封装到了几个类中
  5. 将一些具有相同功能的代码封装到了一个程序集中(dll、exe),并且对外提供统一的访问接口。(属性名、方法名等)

子类继承父类的属性和方法,使创建子类变得简单,实现代码重用、以及多态。
类的单根继承性、传递性、继承时构造函数的问题。
构造函数不能被继承。
所有的类都直接或间接的继承自object。查看IL代码。

如果类没有写继承,则默认继承自object。
继承具有单根性(即只能继承一个类或方法)
比如:
class Person:Animal

LSP里氏替换原则(需要一个父类类型时,给一个子类类型对象也是可以的)
比如父类时Person,子类是Student。
Person p1 = new Student();
(程序的可拓展性、灵活性)方法重写override。虚方法的原理:虚方法表。

无法将父类赋值给子类,但是可以强制转换
Student s1 = (Student)p1;

构造函数问题
当一个子类继承父类后,该子类中的所有构造函数默认情况下,在自己被调用之前都会先调用一次父类的无参数的构造函数。如果此时父类中没有无参数的构造函数,则提示报错!
如果报错?
解决办法一:在父类中添加一个无参数的构造函数。
解决办法二:在子类的构造函数后面通过:base()的方式,明确指定要调用父类中的那个构造函数。
:base()表示调用父类的构造函数,构造函数是不能被继承的。
public Student (string name, int age, int height, string sid):base(name, age, height)
{
​ this.SID=sid;//如果父类中没有这个参数,可以这样子手动赋值
}

1-2-7(this与base区别,构造函数间的调用)

this:指当前类,this调用当前类的属性,方法,包括构造函数的方法,继承本类的构造函数
base:指当前类的父类,可调用父类的非私有属性,方法,继承父类的构造函数括号里的参数

构造函数间的调用,使用this来调用本类中的其他构造函数(调用成员,自己)
在构造函数后用:this(name,0,height,string Empty)调用其他构造函数
如果是不需调用字符,可以写成string Empty;
如果是不需调用数值,可以写成0;
Public Person(string name, int height) :this(name,0,height,string Empty)
{
​ this.Name=name;
​ this.Height=height;
}

Public Person(string name,string email):this(name,0,0,email)
//相当于使用了Person(string name, int age, int height, string email)
//只是对里面进行了name,email传参
{
​ this.Name=name;
​ this.Email=email;
}
Public Person(string name, int age, int height, string email)
{
​ this.Name=name;
this.Age=age;
​ this.Height=height;
​ this.Email=email;
}

this:
1.作为当前类的对象,可以调用类中的成员。this.成员(调用成员,自己)
2.调用本类的其他构造函数。this()(调用构造函数,自己)

base:

  1. 调用父类中的构造函数(在子类重写父类成员或者子类使用new关键字隐藏了父类成员,调用父类成员时,调用父类成员)base点不出子类独有成员
  2. 调用父类中的构造函数(调用构造函数,父类)
  3. 当调用从父类中继承过来的成员的时候,
  4. 如果子类没有重写,则this.成员;
    与base.成员;没有区别。
    如果子类重写了父类成员,则this.成员;调用的是子类重写以后的。
    base.成员;调用的依然是父类的成员

子类构造函数必须指明调用父类哪个构造函数

1-2-8(“”与null的区别,默认修饰符)

“”与null的区别:
string s = “”;//开启了空间,长度为0,0个字符,空字符串
string s1 = null;//没有开辟空间

修饰符

  1. public//任何地方都能访问
  2. protected internal//同时具有protected与internal的访问权限
  3. internal//在当前程序集的内部
  4. protected//当前类内部以及所有子类的内部
  5. private//只有当前类内部可以访问
    类的成员变量,如果不写访问修饰符,默认是private
    类本身如果不写访问修饰符则默认为internal

1-2-9(访问级别约束,访问修饰符一致,虚方法virtual)

访问级别约束

  1. 子类的访问级别不能比父类的高(会暴露父类的成员)
  2. 类中属性或字段的访问级别不能比所对应的类型访问级别高
  3. 方法的访问级别不能比方法的参数和返回值的访问级别高

方法的访问修饰符需要和方法的参数的类型的访问修饰符一致
方法的参数与方法的返回值都必须得和方法保持一致的访问修饰符

虚方法virtual
虚方法可以给父类中的方法一个实现,比如ToString()方法
虚方法必须有实现部分,哪怕是空实现
案例:员工类、部分经理类(部门经理也是员工,所以要继承自员工类。员工有上班打卡的方法。用类来模拟)
虚方法和抽象方法的区别

子类重写父类方法时,必须与父类保持一致的方法签名与返回值类型。即:方法名、返回值类型、参数列表都必须保持一致。【访问修饰符也得一致】
“方法签名”:一般是指方法的【名称】+方法的【参数列表】,不包括方法返回值类型

1-3(方法签名,多态,里氏替换,抽象类,“引用传递”,“值传递”)

方法签名:一般是指方法的【名称】+方法的【参数列表】,不包含方法返回值类型
子类重写父类方法时,必须与父类保持一致的方法签名和一致的 返回值类型。即:方法名、返回值类型、参数列表都必须保持一致。【访问修饰符也要一致】

类中的静态成员,在第一次使用静态类的时候进行初始化

静态构造函数的特点:

  1. 不能添加访问修饰符,不能手动调用,而是在第一次使用静态类的时候自动调用的,所以不能为静态构造函数添加访问修饰符,默认为private
  2. 因为静态构造函数是系统自动调用的,所以不需要(不能)添加任何参数
  3. 静态函数只执行一次,在第一次使用静态类或静态成员的时候执行,不确定什么时候执行
  4. 先执行静态函数,再执行构造函数

多态就是指不同对象收到相同消息时,会产生不同行为,同一个类在不同的场合下表现出不同的行为特征

里氏替换原则:
父类引用指向子类对象
person p = new Chinese();
//隐式类型转换
父类对象不能够替换子类

person p = new person();
//student s = (student)p;
//通过这种方式进行类型转换时,如果转换失败则直接报异常
student s = p as student;
//通过as的方式进行类型转换,及时转换失败也不会报异常,而是返回一个null

抽象类abstract(不能被实例化的类<不能new>)
抽象类不能被实例化,抽象类可以有普通成员
抽象类存在的意义:

  1. 抽象类不能被实例化,只能被其他类继承
  2. 继承抽象类的子类必须把抽象类中的所有抽象成员都重写(实现)(除非子类也是抽象类)
  3. 抽象类就是为了重写→多态(代码重用)
  4. 抽象类中可以有实例成员也可以有抽象成员
  5. 抽象成员不能有任何实现
  6. 抽象成员必须包含在抽象类中
  7. 抽象类不能用来实例化对象,就是用来被继承的,主要目的用来实现多态
  8. 抽象成员子类继承以后必须“重写”override,除非子类也是抽象类

值类型均隐式派生自System.ValueType
1. 查看类型、bool、结构、枚举
2. 查看IL,隐式继承自ValueType
引用类型派生自System.Object

  1. 字符串、数组、类、接口等
  2. 查看IL隐式继承自Oject

引用类型赋值的时候是将栈中的地址拷贝了一个副本(只复制对对象的引用)
值类型赋值的时候将栈中的数据拷贝了一个副本(拷贝一个副本)

“引用传递”传递的是栈本身的地址(ref)
“值传递”传递的是栈中的内容,是将栈中的内容拷贝了一个副本

1-3-1(this与base调用方法的不同,静态类,静态成员)

class ParentClass
{
​ public virtual void M1()
​ {
​ Console.WriteLine(“爷爷类中的M1”);
​ }
}
class A:ParentClass
{ //备注
​ public new void M1()
​ {
​ Console.WriteLine(“A类中的M1”);
​ }
}
class B:ParentClass
{
​ public override void M1()
​ {
​ Console.WriteLine(“B类中的M1”);
​ }
}
备注:A类处,这个表示子类全新的添加了一个M1方法,为什么可以添加一个和父类中M1方法一模一样的方法呢?因为这里用了new关键字,将从父类中继承下来的那个M1方法给隐藏掉了。所以此时,这个类中只有一个M1方法,通过this.M1()调用的一定是子类中的全新的这个M1方法,但是如果通过base.M1()则调用的是父类中原来的那个M1方法。

在静态类中,所包含的所有成员必须都是“静态成员”
不是所有静态成员都必须写在静态类中属性名
静态成员是属于“类”的,不是属于具体“对象”的
所以访问静态成员的时候不能通过对象来访问,(对象.属性名),只能通过“类名”来直接访问静态成员,比如:类名.成员名
不可用this调用静态方法

静态成员数据直到程序退出才会释放资源,
而实例对象,只要使用完毕就可以执行垃圾回收。

实例化私有对象
Class1 c = Class1.GetObject();

Public class Class1
{
​ private Class1()
​ {

    }
    Public  static  Class1 GetObject()
    {
        return new Class1();
    }

}

1-4(方法中的new的作用,参数传递方式,ref与out传递转换,接口,异常处理,对象相同判定)

new 截断,把从父类继承下来的方法隐藏掉了

参数传递方式

  1. 值传递,默认参数传递方式就是值传递
  2. 引用传递。ref
    overload方法重载(一个类里面的几个方法,程序编译时能确定调用哪个方法)
    override方法重写(父子类之间的方法,只有在程序运行的时候确定调用哪个方法)

“光说不做”------接口(以大写I开头)
接口存在的意义:多态(增加程序可拓展性,使程序变得更加灵活)
接口之间可以实现多继承
接口不能实例化(让子类实现)

接口里只能包含”方法”(方法、属性、索引器、事件)
接口成员不写访问修饰符(默认是public),不定义,不实现
//在接口中这样写表示是一个未实现的属性
string Name
{
​ get;
​ set;
}
//索引器
string this[int index]
{
​ get;
​ set;
}

当一个类同时继承父类,并且实现了多个接口的时候,必须将继承类,写在第一个

显示实现接口(只有方法重名时才使用)
void 接口名.方法名(){}
显示实现接口后,只能通过接口来调用。显示实现接口默认为private。不能通过对象本身来调用(显示实现接口,查看IL是private,防止通过类来调用)

接口→抽象类→父类→具体类

类型转换
任意类型转换成字符串(ToString())

把字符串转换换成“数值类型”
int.Parse(string str);
int.TryParse(string str,out int n);
double.Parse(string str);
double.TryParse(string str,out double d);
Parse()转换失败报异常
TryParse()转换失败不报异常

as与直接类型转换
Student stu = (Student)p;
String.GetType().BaseType().BaseType().ToString();//获取类型.获取父类
Student stu = p as Student;//转换失败返回null,而不会报异常。

try{}catch{}finally{}
try块:可能出现的异常代码。当遇到异常时,从try块中该异常代码开始后续代码不执行。
catch块:对异常的处理。记录日志(log4net),继续向上抛出操作。(只有发生了异常,才会执行。)
当catch块中有return语句时,在执行return语句之前无论如何都会先执行finally中的代码,finally后的代码不执行。
finally块:代码清理、资源释放等。无论是否发生异常、是否捕获异常都会执行。不能写return语句

throw new Exception(“手动抛出异常提示”);
throw;//表示将当前异常继续向上抛出。这种写法只能在catch块中写
Exception类主要属性:Message、StackTrace、InnerException(*)
扔出自己的异常,扔:throw,抓住:catch
如果发生异常,会终止相关方法的调用,并且释放相关的资源。
try→catch(一个或多个,也可以没有)→finally(只能有一个,可以没有)不能同时没有catch和finally

为方法的值单独创建变量,方法的返回值也单独创建变量

params
1.可变参数(必须出现在参数列表的最后,可以为可变参数直接传递一个对应类型的数组)
2.可变参数可以传递参数也可以不传递参数,如果不传递参数,则args数组为一个长度为0的数组
3.可变参数可以直接传递一个数组进来

ref(仅仅是一个地址,引用传递,可以把值传递强制改为引用传递)

  1. 参数在传递之前必须赋值
  2. 在方法中可以不为ref参数赋值,可以直接使用

out(让函数可以输出多个值)

  1. 在方法中必须为out参数赋值
  2. out参数的变量在传递之前不需要赋值,即使赋值了也不能在方法中使用。(赋值没意义)
  3. 在使用之前必须在方法里面为out参数传递
  4. 无法获取传递进来的变量中的值,只能为传递进来的变量赋值
    5.在方法执行完毕之前,必须赋值

ref应用场景内部对外部的值进行改变
out则是内部为外部变量赋值
out一般用在函数有多个返回值的场所

两个对象内存相同,则说是相同对象(堆地址相同,栈地址可以不一样),否则就不相同
object.ReferenceEquals(对象名1,对象名2);
对象名1. Equals(对象名2);
(对象名1==对象名2)

//为true,则两对象相同。
//为fause,则两对象不相同。

1-5(对象判定,sealed无法继承,字符串和字符串池,垃圾回收,弱引用,文件流)

object.ReferenceEquals(对象名1,对象名2);//最靠谱,可以比较两个变量是否是同一对象

对象名1. Equals(对象名2); //对于字符串类型来说,重载了Equals()方法,判断字符是否完全一样

(对象名1==对象名2) //对于字符串类型来说,重载了Equals()方法,判断字符是否完全一样

sealed

1. 在类前写,表示此类无法再被继承(string类不能被继承)【密封类】

2. 在方法前写,表示此类无法再被重写

abstract

1. 无法被实例化

2. 无法被创建对象

字符串一旦被创建,就不可变性

1.调用后,必须要返回值接收被改变后的值

字符串池(针对字符串常量)

String.Intern(xx),Intern方法使用暂存池来搜索与str值相等的的字符串。如果存在这样的字符串,则返回暂存池中他的引用。如果不存在,则向暂存池添加对str的引用,然后返回该引用。

String.IsInterned(xx),此方法在暂存池中查找str。如果已经将str放入暂存池中,则返回对此实例的引用;否则返回nullNothingnullptrnull引用。

为什么字符串类前要加sealed关键字?

  1. 子类如果继承字符串类以后可能会对字符串类进行修改可能会改变字符串的特性。
  2. CLR对字符串提供了各种特殊的操作方式,如果有很多类先继承了字符串类,则CLR需要对更多的类型提供特殊操作,这样有可能会降低性能。

字符串格式化

Console.WriteLine(“=我有{0,10:C2}工资”,2345.5);

{代替位置,长度(为正则右,反之为左):格式+保留小数点后多少位}

char[] chs = new char[]{‘a’,’b’,’c’};

string s = new string(chs);

Console.WriteLine(s);

Console.WriteLine(chs.ToString());//char数组没有重写ToString()方法

Console.Readkey();

输出结果为:

abc

System.Char[]

参数名.Substring(起始位置索引,截取长度);//如果不传截取长度,表示截取到最后

string[] line=File.ReadAllLines(“文本名”,Encoding.Default);//获取文本内容

参数名.Split(new char[]{‘-’},StringSplitOptions.RemoveEmptyEntries);

//去除参数中含有’-’的值

参数名[0].Split(‘=’)[1];//分隔后取缔第一个

参数名.Join(‘-’, 参数名); //添加’-’到参数中,可以进行数组分隔

String对象是不可变的。每次使用System.String类中的一个方法时,都要在内存中创建一个新的字符串对象。这就需要为该新对象分配新的空间。

如果要重复修改、大量修改字符串而不创建新的对象,则可以使用System.Text.StringBuilder类。(提示性能)

StringBuilder!=String;//将StringBuilder转换为String用ToString();

StringBuilder仅仅是拼接字符串的工具,大多数情况下还需要把StringBuilder转换为String

StringBuilder sb= new StringBuilder();

sb.Append();

sb.ToString();

sb.Insert();

sb.Replace();

Stopwatch watch = new Stopwatch();

watch.start();

watch.stop();

Console.WriteLine(watch.Elapsed);//打印持续时间

垃圾回收,提高内存利用率(.net CLR自动执行,一般不需要手动回收)

垃圾回收器,只回收托管堆中的内存资源,不回收其他资源(数据库连接、文件句柄、网络端口等)

  1. 不确定什么时候回收,当程序需要更新内存的时候开始执行回收
  2. GC.Collect();//手动调用垃圾回收器,不建议使用,垃圾回收时会暂停一下(非常短暂)让程序自动去GC。
  3. 只要处在被引用(任何变量,任何对象)状态下,就不能被垃圾回收
  4. 表示可以被回收(对象或变量值为null,没有指向)

GC.Collect();//可以往里传参数0,1,2(表示第几代,共3代),不传

1.先创建第0代对象

2.第0代中对象满了开始垃圾回收,其中还有指向和依然被引用的对象,则转移向第1代中

3.第1代中对象满了开始垃圾回收,其中还有指向和依然被引用的对象,则转移向第2代中

4.如果第2代满了,则会进行内存扩充。如果第2代满了,则报异常

5.第0代最高,其次第1代,再次第2代。也就是说对象越老生存几率越大

.net中垃圾回收机制:mark-and-compact(标记和压缩),一开始假设所有对象都是垃圾

public class Myclass:IDisposable

{

Myclass(0

{}

~Myclass()//析构函数,终结器。donet里没有。自动调用,垃圾回收前,先执行终结器

{}

public void Dispose()//先释放非托管资源,再执行垃圾回收

{}

}

弱引用(当对象可以被垃圾回收时,还未垃圾回收时,还可以引用)

Person p =new Person();

WeakReference wr = new WeakReference(p);//提前弱引用

p=null;

//重新使用p对象

object o =wr.Target;

if(o!=null)

{

Person p1=o as Person;

//然后就可以使用Person对象了(这个对象还是原来的对象)

}

文件流FileStream

File.ReadAllText、File.WriteAllText//进行文件读写是一次性读、写。文件非常大会占内存、慢。需要读一行处理一行的机制,这就是流(Stream)。Stream会只读取要求的位置、长度的内容。

Stream不会将所有内容一次性读取到内存中,有一个指针,指针指到哪里才能读、写到哪里。

FileStream类new FileStream(“c:/a.txt”,filemode,fileaccess)后两个参数可选值及含义。FileStream可读可写。可以使用File.OpenRead、File.OpenWrite这两个简化调用方法。

byte[]是任何数据的最根本表示形式,任何数据最终都是二进制。

FileStream的Position属性为当前文件指针位置,每写一次都要移动一下Position()以备下次写到后面的位置。Write用于向当前位置写入若干字节,Read用户读取若干字符。

  1. 使用using可以方便的释放资源(自动调用Dispose方法)
  2. 只有实现了IDispose接口的类才能使用using释放资源
  3. 只有实现了IDispose接口的类型的对象,才能写在using的小括号里面
  4. 当using()执行完毕时,会自动调用对象的Dispose()方法来释放资源

Stream把所有内容当成二进制来看待,如果是文本内容,则需要程序员来处理文本和二进制之间的转换

用StreamWriter是辅助Stream进行处理的

using(StreamWriter writer = new StreamWriter(stream,encoding))

{

writerWriteLine(“你好”);

}

和StreamWriter类似,StreamReader简化了文本类型的流的读取。

Stream stream =File.OpenRead(“c:/1.txt”);

using(StreamWriter writer = new StreamWriter(stream,encoding))

{

//Console.WriteLine(reader.ReadToEnd());

Console.WriteLine(reader.ReadLine());

}

ReadToEnd用于从当前位置一直读到最后,内容大的话会占内存;每次调用都往下走,不能无意中调用了两次

ReadLine()读取一行,如果到了末尾,则返回null

推荐阅读