C#笔记誊录<二>
c#编译器把源码编译成IL(中间)代码,再由CLR通过即时编译器把IL代码编译成本机机器语言(平台语言)
www.webkaka.com//测试服务器的网速
ctrl+k+d 代码对其 CTRL+K+S #REGION代码折叠 ctrl+r+e 连敲2个回车:封装字段 shift+alt+f10:实现接口
XML序列化:把对象写到XML文档中
标识某个对象的某个字段不能被序列化,只要把[XmlIgnoreAttribute]放在某个字段前面
二进制流序列化:把对象序列化成二进制流进行网络传输
标识某个对象的某个字段不能被序列化,只要把[NonSerialized]放在某个字段前面
“序列化”可被定义为将对象的状态存储到存储媒介中的过程。在此过程中,对象的公共字段和私有字段以及类的名称(包括包含该类的程序集)都被转换为字节流,然后写入数据流。在以后“反序列化”该对象时,创建原始对象的精确复本。
一、为什么要选择序列化
一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本;
另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。
例如,序列化可用于在 ASP.NET 中保存会话状态并将对象复制到 Windows 窗体的剪贴板中。
远程处理还可以使用序列化通过值将对象从一个应用程序域传递到另一个应用程序域中。
序列化的目的: 1、以某种存储形式使自定义对象持久化;
2、将对象从一个地方传递到另一个地方。
实质上序列化机制是将类的值转化为一个一般的(即连续的)字节流,然后就可以将该流写到磁盘文件或任何其他流化目标上
XML的一般用处:配置文件,保存静态数据,一般用于webservice和webconfig文件中
反射:程序运行时,动态获取程序集的相关信息,类,方法等。动态创建的类的实例,调用类中的方法,访问类中的属性。
一般用于:开发插件,动态加载程序组件,动态更换底层数据库类型
hashset用来存放不重复的数据
hashset<int> set=new hashset<int>()
set.add(5);
set.add(3);
set.add(5);
//最后输出没有重复的数据
静态变量的存储字段到程序结束的时候才回收,在整个项目中相当于全局变量,如果用多了,会很占内存
Random r = new Random();
r.Next(1,16);//返回一个1到15非负的随机数
r.Next()///返回一个非负的随机数
r.Next(151)//返回一个0到150的非负随机数
各种变量的默认值:bool:false,数字int:0,字符char:\0,引用类型(string):null
在监视窗口中
值类型前面加个&,查看内存地址
引用类型前面加个*,查看内存地址
32位计算机,用32个二进制位来表示一个整数,同时使用32个二进制位来表示一个内存地址
一个内存空间放运行方法中的代码--线程栈
一个内存空间放对象名--托管堆
存放大对象,静态对象
每个INT占4个字节,64位8个字节
垃圾收集器:手动调用,system.gc.collect(0);会根据你系统当前的内存的使用情况进行优化
每次进行
属性:
//一般的属性,本质就是一个方法,后台提供的get,set的方法
private static string ploginname; public static string Ploginname { get; set; }
//自动属性,方便的时候用,只是存取单个值,没有复杂的逻辑的功能,没有特殊的控制要求的时候
//get和set必须成对存在
//系统后台生成Type字段,用于保字段的值
public static string Type { get; set; }
//抽象属性 在抽象类中存在的
//抽象属性与自动属性很相识,只是在前面加了个abstract来修饰而已
//后台没有方法体,因此可以是只读,而自动属性不可以,必须get和set成对出现
abstract class MyBase { public abstract string Test//要求子类重写 { get; set; } //抽象的只读属性 public abstract string Test2 { get; } public abstract string Test1 { get; set; } } class MySub : MyBase { string test;//子类必须提供字段 public override string Test//重写属性 { get { return test; } set { test = value; } } //此时重写基类的属性,变成了自动属性,后台生成字段来保存变量的值 public override string Test1 { get; set; } //重写的时候需要注意,子类只重写父类提供的,如果父类只提供了只读属性,则子类 //也只能重写只读的,而不能加set进去 public abstract string Test2 { get{return test;} } }
栈:由编译器自动分配、释放。在函数体中定义的变量通常在栈上,一般存放值类型的变量。
堆:一般由程序员分配释放。用new、malloc等分配内存函数分配得到的就是在堆上,一般存放引用类型。
构造函数(方法):
声明空构造函数可阻止自动生成默认构造函数。注意,如果您不对构造函数使用访问修饰符,则在默认情况下它仍为私有构造函数。
但是,通常显式地使用 private 修饰符来清楚地表明该类不能被实例化。
一、没有继承时:
1、默认构造方法(无参数构造方法)
1、没有提供构造方法的时候,系统自动添加一个无参数的构造方法,如果已经写了构造方法,那么系统就不会自动添加默认构造方法,
需要自己手动添加
构造方法的重载
1、当调用的指定构造函数后有this的时候,会先调用this表示的构造方法,再调用自己的
二、有继承关系的时候:
1、默认调用父类的无参构造方法(后面只是隐藏了base:())
2、使用base可以指定调用父类的构造方法
1、继承时,子类可以选择性的调用基类的构造函数(通过函数的重载),子类:base(i)
2、继承时,子类构造函数默认继承基类的无参构造函数,子类:base(),此时必须保证基类有无参构造函数
3、类中,原始构造函数初始化时,使用 构造函数:this(),来继承最原始的默认构造函数
4、在创建对象的时候,先调用父类构造方法,再调用子类构造方法
5、在调用构造方法的时候,其实默认有一个base(),调用了父类无参的构造方法
public MyClass() { InitialCompoment();//原始构造方法,有这个类中所有字段的初始化操作 } public MyClass(string n):this() { // 再调用带有参数string n这个构造方法之前,先去调用标准的构造方法 Console.WriteLine(n); }
base关键字:1、表示父类 2、父类的构造方法(指定调用哪一个父类构造函数)
this关键字:1、表示当前类 2、表示当前构造方法(指定先调用哪一个当前类的构造函数)
继承关系中的构造函数:
// 子类与父类构造方法的执行顺序:先调用父类的,再调用子类的
// 如何根据父类构造方法的重载,进行指定调用:使用base:()//public SubClass():base("1")
// 使用构造方法初始化各个字段(在继承中通过参数传值)
public Student(string name, int age, char gender, int score) // 子类构造方法的定义 : base(age, gender, name) // 父类构造方法的调用 { // 调用子类构造方法时,先调用父类构造方法,这里的base是在调用父类构造方法 _score = score; }
不存在继承关系的构造函数:
//当构造函数中后面还有:this的时候,在调用本身之前回去调用:后面的构造函数,当new MyClass()的时候会去调用 MyClass(int i),在调用MyClass(int i)时候会跑去调用MyClass(int i, int j)
//然后再一个个返回执行自己
//如下所示:
class MyClass { public MyClass():this(3) { Console.Write("1"); } public MyClass(int i):this(2,3) { Console.Write(i); } public MyClass(int i, int j) { Console.Write("{0}{1}", i, j); } } class Program { static void Main(string[] args) { new MyClass();//输出结果为23 3 1 Console.ReadKey(); } } //InitialCompoment()原始构造方法 public MyClass() { InitialCompoment(); } public MyClass(string n):this() { // 再调用带有参数string n这个构造方法之前,先去调用标准的构造方法 Console.WriteLine(n); }
抽象方法:没有方法体的方法用abstract修饰,叫做抽象方法
抽象类:包含抽象方法的类(有些时候,父类的方法不需要实现,有些时候,不知道怎么去实现)
1、不能被实例化,比一般类多个抽象成员
2、抽象方法:没有方法体的方法用abstract修饰,叫做抽象方法
3、抽象类中可以包含非抽象成员
4、包含抽象方法的类,也必须是一个抽象类
抽象类的成员有哪些
1、方法、属性、事件、索引、字段
2、可以包含非抽象成员
3、不能被实例化,只能实例化子类
4、子类可以不实现抽象类中的抽象方法,但是必须保证子类也是抽象类。
类是对某个具体对象的抽象、对象是类的实例化
接口的对功能的抽象
接口:
同样的方法对不同的对象表现为不同的行为,同一操作作用于不同的对象,产生不同的执行结果。
1、类的定义的一般格式:[public][static] class 类名:[基类],[接口1],[接口2],..
2、接口的语法:
1、接口声明以I开头
2、接口中的方法没有修饰符
3、无实现体
4、接口可以继承多个接口 接口:接口1,接口2,接口3
显示实现接口:2个接口包含相同的方法时,要实现方法时,直接接口1.方法名,去掉前面的修饰符
包含的成员:方法、属性、事件、索引(没有字段)
接口只能定义方法(只能定义行为,不能定义实现也就是字段),因为事件、索引器、属性本质上都是方法,所以接口中也可以定义事件、索引器、属性。
ASCCI:1字节
UNICODE:任何都占2个字节
接口与类是平行关系,接口不继承Obect,一般类也不实现与接口
里氏转化:(父类的引用指向子类的对象) // 1、子类可以直接赋值给父类(子类对象可以直接转化为父类对象) // 2、如果满足第一个条件的父类对象,可以强制转化为原来的子类对象 父类 父类对象 = new 子类(); 子类 子类对象 = (子类)父类对象; Protocal protocal = new iPhone6(); iPhone6 i = (iPhone6)protocal; //子类必须能替换父类,反过来不一定,父类能替换子类的前提是:该父类对象是指向该子类对象 子类 子类变量=父类 as 子类 子类 子类变量; if(父类变量 is 子类) { 子类 子类变量=(子类)父类变量; } else { 子类 子类变量=null; }
静态类:
和所有类类型一样,当加载引用静态类的程序时,.NET Framework 公共语言运行时 (CLR) 将加载该静态类的类型信息。
程序不能指定加载静态类的确切时间。但是,可以保证在程序中首次引用该类前加载该类,并初始化该类的字段并调用其静态构造函数。
静态构造函数仅调用一次,在程序驻留的应用程序域的生存期内,静态类一直保留在内存中。
对于只对输入参数进行运算而不获取或设置任何内部实例字段的方法集,静态类可以方便地用作这些方法集的容器
静态类的主要特性:
•仅包含静态成员。
•无法实例化。
•是密封的。
•不能包含实例构造函数。
因此,创建静态类与创建仅包含静态成员和私有构造函数的类基本相同。私有构造函数阻止类被实例化。使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实利。
静态类是密封的,因此不可被继承。它们不能从除 Object 外的任何类中继承。静态类不能包含实例构造函数,但可以包含静态构造函数。如果非静态类包含需要进行重要的初始化的静态成员,也应定义静态构造函数。
静态成员:
非静态类可以包含静态的方法、字段、属性或事件。即使没有创建类的实例,也可以调用该类中的静态成员。始终通过类名而不是实例名称访问静态成员。无论对一个类创建多少个实例,它的静态成员都只有一个副本。静态方法和属性不能访问其包含类型中的非静态字段和事件,并且不能访问任何对象的实例变量(除非在方法参数中显式传递)。
更常见的做法是声明具有一些静态成员的非静态类,而不是将整个类声明为静态类。静态字段有两个常见的用法:一是记录已实例化对象的个数,二是存储必须在所有实例之间共享的值。
静态方法可以被重载但不能被重写,因为它们属于类,不属于类的任何实例。
字符串:
string str= string .Empty();//堆内存在空字符; str=null;//没有开辟堆内存,不指向任何地址。 str.isNULLorEmpty();判断是否为空。 字符串的不可变性: 每次创建一个字符串的时候都开辟一个不同的内存地址 char[] ch={'1','2'}; string str3=new string(ch); 字符串拘留池(暂存池) string str1="123"; string str2="123";//如果存在大量字符串值相同,则内存地址相同,只创建一个地址。 此时的str1,str2,内存地址相同 所以一般是只要暂存池
字符串的处理:
1、比较:相等、比较相等:“==”、Equals方法 2、修整:去掉前后空格,去掉前空格、去掉后空格、去掉数组中的字符:trim() 3、检索:插入、移除:insert/remove 4、分割与合并:spit()/jion() 5、格式化:string str = string.Format("{0}",1);
//把list对象中内容进行换行输出的办法
1、List<string> list=new List<string>(); list.add(); textBox3.Text = string.Join("\r\n", list.ToArray());//把list先转化成数值,然后用换行符进行连接即可 2、StringBuilder sb = new StringBuilder(); for (int i = 0; i < list.Count; i++) { sb.AppendLine(list[i]); } textBox3.Text = sb.ToString(); sb.AppendFormat("{0}\t{1}\t{2}\r\n",1,2,3,4);//使用sb的格式化来追加字符
@符号:预格式字符串:代码中字符串允许换行,取消转义字符,保存到文本中的内容也是换行显示的
c#中的\表示转义字符,若要表示文本中的\,就必须前面加一个\,如\\,或者是在前面加一个@符号,来取消转义功能,就表示文本中的\。
\r\n:换行,\":双引号,\\:一个\,\\\\:表示\\,\b:向前移一个位置,挡掉前面的,\t: 表示一个空格tab,\s:空格
Mian()方法:文件名 参数1 参数2 “1 2”//第一个参数文件名,后面参数以空格来间隔,要包含空格的可以用“”来包含,在cmd命令中运行:文件名 参数1 参数2 “1 2”
string str = "我¨°又??变à?帅?ì了¢?";
//string转字节
// byte[] bytes = Encoding.UTF8.GetBytes(str);//把字符串str以编码UTF8的格式转化成字节数组
byte[] bytes = Encoding.Unicode.GetBytes(str);//
字节转string
string temp = Encoding.Unicode.GetString(bytes);//用什么方法转的就用什么方法解
Path类:针对字符串进行处理,不管路径是否存在
string path=@"d:\music\天黑黑.mp3";
1、改后缀名:path=Path.ChangeExtension(path,"lrc"); 2、获得文件所在的文件夹:Path.GetDirectoryName(path)//d:\music 3、获取文件名称:Path.GetFileName(path);//天黑黑.mp3//也可以获取文件夹的名字 4、获取后缀名:Path.GetExtension(path);//.mp3 5、拼接2个路径:Path.Combine("D:\\1\\music","真的爱你.mp3");//D:\1\music\真的爱你.mp3 Path.Combine("D:\\1\\music\\","真的爱你.mp3")//前面最后结尾处加不加\\,效果同上D:\1\music\真的爱你.mp3 Path.Combine("D:\\1\\music\\","C:\真的爱你.mp3")//返回后面一个C:\真的爱你.mp3 6、获取全路径:Path.GetFullpath(文件名);//一般用的较多 string path = Path.GetFullPath("txt");//获取“txt”文件夹的全路径:C:\Users\Administrator\Documents\Visual Studio 2008\Projects\福尔摩斯小说阅读器\福尔摩斯小说阅读器\bin\Debug\txt 7、获取临时文件路径:Path.GetTempPath();//一般是C盘下面的temp文件 8、获取临时文件路径名称:Path.GetTempFileName()//获取C盘下面的temp文件临时文件名称
启动一个进程:
System.Diagnostics.Process p=new System.Diagnostics.Process(); p.StartInfo.Arguments="path";//全路径 p.StartInfo.FileName="notepad";//应用程序名 p.start(); FileStream类: FileStream("path",FileMode); FileStream("path",FileMode,FileAccess);
读:
FileStream fs=FileStream("path",FileMode,FileAccess); int res=fs.ReadByte();//按文本中的内容,字节一个一个的读取,返回的是ASIC编码,如文本中是1,返回49,此时要显示出来1要进行转换(char)res; //采用循环来读取文本中的内容:while((res=fs.ReadByte())>=0) { cw((char)res); } Read(byte缓存数组,数组中的位置,读取多少数据);
写:
WriteByte(byte)//将一个字节写入到流里去 fs.WriteByte(97)//等于就是写了一个a到文本中去 for(int i=97;i<97+26;i++) { fs.WriteByte((byte)i);//写26个字母到文本中,int 4个字节,byte 2个字节,所有要(类型兼容)强转 } Write(byte缓存数组,数组中的位置,读取多少数据);
控制:
Flush();//清空缓存区,并把数据写入到文件中
Position当前流(读写的)所在位置,可以获取也可以设置,可以根据流的位置来获取或设置内容
Seek(偏移量,位置枚举);在当前位置向右/左移动多少个位置,正数向右,负数向左,file.seek(10,CurrentPosition):在当前流的位置向右偏移10个位置。
Close()与Dispose();//释放非托管资源
using语句,一般用来释放非托管资源
内存流与网络流
MemoryStream/NetworkStream
读写流
StreamRead/StreamWrite针对文本文件
先实例化一个文件流:FileStream fs=new FileStream(path,mode,access);
在实例换一个StreamRead sr=new StreamRead(fs,Encoding.Default);//出现乱码时,重载函数加编码进去或者(fs,Encoding.GetEncoding("gb2312"))用国标码来获取中文
sr.ReadLine//一行一行读;
sr.ReadToEnd//从开始读到结束
//写:write.Write(string);
write.WriteLine(?);写一行
string[] files = Directory.GetFiles("dll", "*dll");
Assembly asm=Assembly.LoadFile(Path.GetFullPath(files[i]));
if (File.Exists("CL.dll"))
{
string path = Assembly.GetExecutingAssembly().Location;//获取应用程序目录
path = Path.GetDirectoryName(path);//获取不包括程序名.exe的目录
string newpath = Path.Combine(path, "CL.dll");拼接
Assembly ms = Assembly.LoadFile(newpath);
}
文件操作
获得当前目录:Directory.getCurrentDirectory();路径容易被改动因为可以使用set方法进行修改,一般不用 path=Assembly.getExectingAssembly().lacation;获得当前的应用程序目录(包括应用程序的名称:123.exe) path=Path.GetDirectoryName(path)//根据上面获得的路径来获取应用程序所在的目录 path.getcurrentDirectory(); Application.StartupPath:程序启动的路径 对文件的操作: File.Creat(path); File.Delete(path); File.AppendLines(path,string); File.AppendAllText(path,string,Encode);//编码不写默认是UTF8 File.ReadAllLines(path,Encoding.Default);//返回一个字符串类型的数值 File.ReadAllText(path,Encoding.Default);//读中文的时候有乱码得加Encoding.Default 对文件夹的操作 Directory Directory.Creat(path); Directory.Delete(path);//此时如果文件夹中存在文件则会报错 Directory.Delete(path,True)//此时如果文件夹中存在文件,照样删除 Directory.GetFiles(文件夹path);//返回当前文件夹中的所有文件 string[] fs = Directory.GetFiles("txt","*.txt");//txt\柯南·道尔简介.txt:传哪个目录进去,就可以获取该目录下文件(不包括子文件夹) Directory.GetFiles(文件夹path,"*.txt")//刷选当前目录下的所有.txt的文件 Directory.GetDirectories(文件夹path);//返回当前目录下所有(包含子文件夹)的文件 一般情况下少用静态类,对大量的文件及文件夹进行操作的时候用File/Directory FileInfo与DirectoryInfo功能与上面2个相同,只是得先创建对象的实例,才能调用 一般对同一个文件进行操作的时候用 string path6 = Assembly.GetExecutingAssembly().Location;//C:\Users\Administrator\Documents\Visual Studio 2008\Projects\获取各种文件路径的操作\获取各种文件路径的操作\bin\Debug\获取各种文件路径的操作.exe string path = Path.GetDirectoryName(path6);//根据上面获取的完整的应用程序来获取当前应用程序或文件的目录,C:\Users\Administrator\Documents\Visual Studio 2008\Projects\获取各种文件路径的操作\获取各种文件路径的操作\bin\Debug string path1 = Path.GetExtension("txt.txt");//获取扩展名.txt //Path.Combine(path7, path8);//连接路径 //Path.ChangeExtension(path9, ".dll");//修改扩展名 string path4 = Directory.GetCurrentDirectory();//获取应用程序的目录bug目录,这种模式不安全,一般不用,很容易被修改, C:\Users\Administrator\Documents\Visual Studio 2008\Projects\获取各种文件路径的操作\获取各种文件路径的操作\bin\Debug // Directory.SetCurrentDirectory("G:\\");//容易被修改 string path3 = Path.GetFullPath("file");//获取包括该文件夹的全路径C:\Users\Administrator\Documents\Visual Studio 2008\Projects\获取各种文件路径的操作\获取各种文件路径的操作\bin\Debug\file string[] dirs = Directory.GetDirectories(path3);//获取包含子文件的文件夹 //string path2 = Path.GetFileName(dir);//根据上面循环获取的文件夹,来获取文件夹名字 string[] files = Directory.GetFiles(path3);//获取该目录下的所有文件(不包括拥有子文件的文件夹) //string[] files = Directory.GetFiles(path, "*.txt");///刷选当前目录下的所有.txt的文件 //string path5 = Path.GetFileName(file);//根据上面循环获取的文件,来获取文件的名字
正则表达式:
判断是否匹配:IsMatch(),字符串提取:Match();循环提取:Matchs();
\(1\)匹配(1),\.匹配.,\-匹配-
-?:表示-出现0次或一次
{3,}:表示匹配前面字符3次以上
(\.\w+)+:紧跟加号前面的字符出现一次或多次,里面的加号表示\w出现一次或多次,外面的加号表示括号里面的全面的内容出现一次或多次(一般用于重复的出现的时候)
():1、表示优先级,一把用于表示里面的字符出现多次,2、分组提取字符串的功能
邮箱:一般验证:\w+@\w+(\.\w+)+
较严谨的:[a-zA-Z0-9_\-]+@\w+(\.\w+)+
获取网页上的图片:src=".+\.jpg"
// src="[^"]+\.jpg"
// @"src=""(.+?)""";//前面加了@符号,里面用2个引号表示一个引号
// string regex = "src=\"(.+)?\"";//同上面
//匹配正确的邮政编码(规定6为数字)
#region MyRegion while (true) { Console.WriteLine("请输入邮政编码"); // string regex = @"^[0123456789]{6}$"; // string regex = @"^[0-9]{6}$"; string regex = @"^\d{6}$"; string res = Console.ReadLine(); if (Regex.IsMatch(res, regex)) { Console.WriteLine("邮编正确"); } else { Console.WriteLine("Error"); } } // 0 1 2 3 4 5678901234 string str = "大家好呀,hello, 20101010年10月10日是个好日子。恩,9494.吼吼!886."; string regex = @"\d+"; Match m = Regex.Match(str, regex);//match的用法 if (m.Success) { Console.WriteLine(m.Value); Console.WriteLine(m.Length); Console.WriteLine(m.Index); } MatchCollection ms = Regex.Matches(str, regex);//Regex.Matches的用法 foreach (Match m in ms) { if (m.Success) { Console.WriteLine(m.Value); Console.WriteLine(m.Length); Console.WriteLine(m.Index); } Console.WriteLine(); } string str = @"姓名:张三,年龄:25,我的性别:男; 姓名:李四,年龄:24,我的性别:男; 姓名:王五,年龄:27,我的性别:男; 姓名:赵倩,年龄:23,我的性别:女"; string temp = @"name='{0}', age={1}, gender='{2}'"; List<string> list = new List<string>(); string regex = @"姓名:(\w+),年龄:(\d+),我的性别:([男女])"; MatchCollection mc = Regex.Matches(str, regex); foreach (Match m in mc) { if (m.Success) { Console.WriteLine(m.Groups[0].Value);//整个匹配出来的结果就是Groups[0],姓名:张三,年龄:25,我的性别:男 Console.WriteLine(temp, m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value);//regex表达式中,第一个()就是第一组Groups[1],第二个()就是第二组Groups[2],以此类推 } }
.:表示匹配任意字符
+:匹配前面字符或组的一个或多个
?:匹配前面字符或组的零个或一个
*:匹配前面字符或组的零个或多个
.+:匹配前面任意字符的一个或多个
提取字符串:用小括号()括起来的内容(\w+)@(\w+(\.\w+)+),想要提取谁就用括号括起来,
string str="<p>12313</p><p>21313</p>"
@“<p>(.+)</p>”贪婪模式,比配全部显示<p>到结尾的</p>:<p>12313</p><p>21313</p>
@“<p>(.+?)</p>”取消贪婪模式,分组显示,此时显示2个:<p>12313</p>、<p>21313</p>
贪婪模式:左边的优先级最高:从左向右,左边第一个贪婪后,其他后面的默认取一个值,当去掉(?)第一个贪婪模式后,第二个开始贪婪,后面的默认取一个值
Regex.Replace替换字符串
string html= Regex.Replace(strHtml,@"\s+"," ");//把多个连续的空格替换成一个 //string str = "aaaasssdddwwwaaadddffaaaeesadw"; //string regex=@"a+"; //str = Regex.Replace(str, regex, "a"); //Console.WriteLine(str); string str = "aaa 213 223 dw 2 d w"; Console.WriteLine(str); string regex = @"\s+"; str = Regex.Replace(str, regex, " "); Console.WriteLine(str); Console.ReadKey();
//分组替换,结合()使用,把Email中的用户名替换成*,输出为“****@163.com.cn”
string email = @"huanjiansheng@163.com.cn"; string regex = @"(.+)(@.+)"; string str = Regex.Replace(email, regex, "*****$2");//当中的$2表示直接用前面的第二组(@.+)匹配到的内容 Console.WriteLine(str);
"******$2",替换前面的第二组
委托与事件的区别:
委托是类型,事件是对象(委托类型的对象)
事件只能在声明的内部访问,不能在外部访问,外部不能触发事件
事件不能像委托一样使用=赋值,必须使用+=
在委托中最后使用=事会把前面的的多播委托给覆盖掉
事件是特殊的委托变量
事件用来阉割委托事例
委托就是一个函数指针(指向一个函数的引用,代表一个方法)
事件就是一个消息机制的实现
委托:
委托是类型,委托就是一个函数指针(指向一个函数的引用,代表一个方法)
允许把方法作为另一个方法的参数进行进行传递
//注意此方法,它接受一个GreetingDelegate类型的方法作为参数
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } GreetPeople("Jimmy Zhang", EnglishGreeting);//EnglishGreeting是个方法,把方法作为参数
可以使用=和+=进行赋值,在委托中最后使用=事会把前面的的多播委托给覆盖掉(所以需要使用事件)
委托表示了对其回调方法的签名,可以将方法当作参数进行传递,并根据传入的方法来动态的改变方法调用。只要为委托提供相同签名的方法,就可以与委托绑定。
委托,实现了类型安全的回调方法。在.NET中回调无处不在,所以委托也无处不在,事件模型建立在委托机制上,Lambda表达式本质上就是一种匿名委托。
委托是一个公开的对象,外部可以对其进行任意的赋值,后面的程序员对其附加方法的时候,不知道前面是否有人为该委托赋过值,可能此时用=号就把前面人注册过的方法给覆盖掉了。
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
delegate1 ("Jimmy Zhang");
//注意这里,第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
//既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:
//使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
//委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
//我们在里将事件声明为public,但是,实际上事件会被编译成私有字段
//如果此时用“=”注册,会编译错误,因为它根本就不允许在事件类的外面以赋值的方式访问,必须得用“+=”进行注册。
//:MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它总是被声明为private。
//另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。
//delegate1 声明为 public 会怎样?结果就是:在客户端可以对它进行随意的赋值等操作,严重破坏对象的封装性。
//delegate1 声明为 private会怎样?结果就是:这简直就是在搞笑。因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,
//客户端对它根本就不可见,那它还有什么用?
//使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
事件:
事件是对象(本质是一个提供了add和Remove方法的委托类型的对象)
事件是特殊的委托变量
事件用来阉割委托事例
事件其实就是为了约束一个委托对象,对外只提供+=,-=操作
对委托对象进行的封装,使改委托的对象变成private修饰,外部不能访问,只能通过其提供的类似属性的公有的add和remove方法进行访问
//对事件的声明实际是 声明一个私有的委托变量
//声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。
//:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。
委托与指针的区别:
委托是类型安全的,面向对象的,有丰富的内部数据结构
指针式不安全的代码,面向过程的,是一个地址的指向
委托链:定义一个数组专门用来存储多个方法的地址
方法指针:方法在内存中的地址,将方法的地址赋给了委托中method变量
DelegateFunc func = new DelegateFunc(Func)//实例化委托的时候必须传一份方法进去,因为构造函数没有采用0个参数的 func += Func1;//然后才可以用+=进行注册 func += Func3; func += Func4; DelegateFunc func;//定义一个委托变量 func = Func;//委托第一次赋值必须得用=号,如果第一次用+=,会报错“使用了没赋值的委托变量” func += Func1; func += Func3; func += Func4; func = Func5;//此时如果使用=号进行赋值,会把前面的值给覆盖掉
而事件:
Computer computer = new Computer(); CloseQQ closeqq = new CloseQQ(); computer.CloseAPP += closeqq.Close;//此时注册方法只能用+=或-+,不能像委托一样使用=号赋值,所以不存在会把前面的值覆盖的可能性 CloseXunlei closexunlei = new CloseXunlei(); computer.CloseAPP += closexunlei.Close; CloseKP closekp = new CloseKP(); computer.CloseAPP += closekp.Close; computer.ShitDown();
程序集:
是net中独有的概念
.exe和.dll都是程序集
程序集(Assembly)可以看作是类库和资源的打包
程序集中有哪些:
1、元数据(并非源数据):程序中用到的所有方法、类、字段等一系列的关系表
2、IL代码
3、资源文件
程序集的好处:
做为类库使用,提供对外的接口
减小类库体积
如何添加程序集:
添加引用
全局程序缓存(GAC):自动加载程序集到运行的环境中的去,加到BUG目录下面去
不能循环添加
//程序集不可以相互引用
//调用同一个命名空间下的不同类和方法,把类库写成小的类库程序集,每个类库包含特定的方法和作用,然后可以
根据需要进行添加引用
//此时他们的命名空间都是:namespace 程序集
程序集.Class3 c3 = new 程序集.Class3();
c3.Find();
程序集.Class1 c1 = new 程序集.Class1();
c1.fuck();
程序集.Class2 c2 = new 程序集.Class2();
c2.Func();
反射:程序集中不添加引用,不更改原来的代码,只通过在BUG目录中添加Dll,就可以自动调用刚添加的Dll里面的方法,通过反射来调用程序集中的方法
Person p=new Person; Type type=p.GetType();//根据实例获取当前对象的信息,获取已知对象的信息 Type type=typeof(Person)//通过运算符来获取(类型),当不知道对象类型的时候使用 然后通过type来获取该对象的所有信息 MedodInfo[] methods=type.GetMethods();//获取该对象的所有方法,然后for循环输出methods[i].Name 获取所有类型: Assembly asm=Assembly.LoadFile(@"c:\TestLIb.dll"); Type[] types=asm.GetTypes(); 获取程序集中的所有的包含Public的类型 Type[] types=asm.GetExportedTypes();/只能获取到public类型的 获取某一个类型:获取class1的相关信息,获取class1的元数据 Type typeclass1=asm.GetType("TestLib.Class1"); 获取class1中的所有方法 MedodInfo[] methods=typeclass1.GetMethods() 获取某个方法 MethodInfo m=typeclass1.GetMethod("SayHi"); 创建一个class1类型的对象 object obj=Activator.CreateInstance(typeClass1); 调用该方法 m.Invoke(obj,null);//第一个参数为对象,第二个参数是调用方法的参数,如果没有则为NULL 验证person类是不是chinese类型的父类// 验证是不是可以把typechinese类型的对象赋值给typeperson类型 bool b=typeperson.IsAssignableFrom(typeChinese);//true 验证是不是继承接口//是否可以将typechinese赋值给typeIxiuFu类型 bool b=typeIxiuFu.IsAssignableFrom(typechinese);//true //验证obj是不是typechinese类型的对象 bool b=typechinese.IsInstanceOfType(obj);//true 验证obj是不是typeperson类型的对象 bool b=typePerdon.IsInstanceOfType(obj);//true 验证typechinese是不是typeperson类的子类 bool b=typechinese.IsSubclassOf(typeperson);//true; 验证typechinese是不是接口类型typeIXiufFu的子类 IsSunclassOf不考虑接口,只考虑父子类的关系,子类只是实现接口,而不是属于子父类的关系 bool b=typechinese.IsSunclassOf(typeIXiufFu);//false 只要不能被实例化都被认为是抽象的 接口、抽象类、静态类都不能被实例化,所以在这里都被认为是抽象的 bool b=typechinese.IsAbstract//false MemberInfo类,有很多子类,大多反射中用到类都是继承它 获取程序集成员的相关信息(类型,方法、事件、字段、属性) ProperInfo:获取属性 设置值SetValue,获取值:GetValue FileInfo//获取字段 EventInfo:获取事件
值类型与引用类型的区别:
值类型继承自ValueType(注意:而System.ValueType又继承自System.Object);而引用类型继承自System.Object。
值类型变量包含其实例数据,每个变量保存了其本身的数据拷贝(副本),因此在默认情况下,值类型的参数传递不会影响参数本身;而引用类型变量保存了其数据的引用地址,因此以引用方式进行参数传递时会影响到参数本身,因为两个变量会引用了内存中的同一块地址。
值类型有两种表示:装箱与拆箱;引用类型只有装箱一种形式。我会在下节以专门的篇幅来深入讨论这个话题。
典型的值类型为:struct,enum以及大量的内置值类型;而能称为类的都可以说是引用类型。 struct和class主要的区别可以参见我的拙作《第四回:后来居上:class和struct》来详细了解,也是对值类型和引用类型在应用方面的有力补充。
值类型的内存不由GC(垃圾回收,Gabage Collection)控制,作用域结束时,值类型会自行释放,减少了托管堆的压力,因此具有性能上的优势。例如,通常struct比class更高效;而引用类型的内存回收,由GC来完成,微软甚至建议用户最好不要自行释放内存。
值类型是密封的(sealed),因此值类型不能作为其他任何类型的基类,但是可以单继承或者多继承接口;而引用类型一般都有继承性。
值类型不具有多态性;而引用类型有多态性。
值类型变量不可为null值,值类型都会自行初始化为0值;而引用类型变量默认情况下,创建为null值,表示没有指向任何托管堆的引用地址。对值为null的引用类型的任何操作,都会抛出NullReferenceException异常。
值类型有两种状态:装箱和未装箱,运行库提供了所有值类型的已装箱形式;而引用类型通常只有一种形式:装箱。
注意点:
1、string类型是个特殊的引用类型,它继承自System.Object肯定是个引用类型,但是在应用表现上又凸现出值类型的特点:
简单的说是由于string的immutable特性,因此每次对string的改变都会在托管堆中产生一个新的string变量,上述string作为参数传递时,实际上执行了s=s操作,在托管堆中会产生一个新的空间,并执行数据拷贝,所以才有了类似于按值传递的结果。但是根据我们的内存分析可知,string在本质上还是一个引用类型,在参数传递时发生的还是按址传递,不过由于其特殊的恒定特性,在函数内部新建了一个string对象并完成初始化,但是函数外部取不到这个变化的结果,因此对外表现的特性就类似于按值传递
2、通常可以使用Type.IsValueType来判断一个变量的类型是否为值类型
MyStructTester aStruct = new MyStructTester(); Type type = aStruct.GetType(); if (type.IsValueType) { Console.WriteLine("{0} belongs to value type.", aStruct.ToString()); }
3、操作符ref和out来标识值类型按引用类型方式传递,其中区别是:ref在参数传递之前必须初始化(传入);而out(传出)则在传递前不必初始化,且在传递时必须显式赋值(函数内部传出之前赋值)。
4、值类型与引用类型之间的转换过程称为装箱与拆箱
5、sizeof()运算符用于获取值类型的大小,但是不适用于引用类型。
6、值类型使用new操作符完成初始化,例如:MyStruct aTest = new MyStruct(); 而单纯的定义没有完成初始化动作,此时对成员的引用将不能通过编译,例如: MyStruct aTest; Console.WriteLine(aTest.X);
7、引用类型在性能上欠于值类型主要是因为以下几个方面:引用类型变量要分配于托管堆上;内存释放则由GC完成,造成一定的CG堆压力;同时必须完成对其附加成员的内存分配过程;以及对象访问问题。因此,.NET系统不能由纯粹的引用类型来统治,性能和空间更加优越和易于管理的值类型有其一席之地,这样我们就不会因为一个简单的byte类型而进行复杂的内存分配和释放工作。Richter就称值类型为“轻量级”类型,简直恰如其分,处理数据较小的情况时,应该优先考虑值类型。
8、值类型都继承自System.ValueType,而System.ValueType又继承自System.Object,其主要区别是ValueType重写了Equals方法,实现对值类型按照实例值比较而不是引用地址来比较
9、基元类型,是指编译器直接支持的类型,其概念其实是针对具体编程语言而言的,例如C#或者VB.NET,通常对应用.NET Framework定义的内置值类型。这是概念上的界限,不可混淆。例如:int对应于System.Int32,float对应于System.Single。
值类型的应用场合
MSDN中建议以类型的大小作为选择值类型或者引用类型的决定性因素。数据较小的场合,最好考虑以值类型来实现可以改善系统性能;
结构简单,不必多态的情况下,值类型是较好的选择;
类型的性质不表现出行为时,不必以类来实现,那么用以存储数据为主要目的的情况下,值类型是优先的选择;
参数传递时,值类型默认情况下传递的是实例数据,而不是内存地址,因此数据传递情况下的选择,取决于函数内部的实现逻辑。值类型可以有高效的内存支持,并且在不暴露内部结构的情况下返回
实例数据的副本,从安全性上可以考虑值类型,但是过多的值传递也会损伤性能的优化,应适当选择;
值类型没有继承性,如果类型的选择没有子类继承的必要,优先考虑值类型;
在可能会引起装箱与拆箱操作的集合或者队列中,值类型不是很好的选择,因为会引起对值类型的装箱操作,导致额外内存的分配,例如在Hashtable。关于这点我将在后续的主题中重点讨论。
3.2 引用类型的应用场合
可以简单的说,引用类型是.NET世界的全值杀手,我们可以说.NET世界就是由类构成的,类是面向对象的基本概念,也是程序框架的基本要素,因此灵活的数据封装特性使得引用类型成为主流;
引用类型适用于结构复杂,有继承、有多态,突出行为的场合;
参数传递情况也是考虑的必要因素;
4. 再论类型判等
类型的比较通常有Equals()、ReferenceEquals()和==/!=三种常见的方法,其中核心的方法是Equals。我们知道Equals是System.Object提供的虚方法,用于比较两个对象是否指向相同的引用地址,.NET Framework的很多类型都实现了对Equals方法的重写,例如值类型的“始祖”System.ValueType就重载了Equal方法,以实现对实例数据的判等。因此,类型的判等也要从重写或者重载Equals等不同的情况具体分析,对值类型和引用类型判等,这三个方法各有区别,应多加注意。
4.1 值类型判等
Equals,System.ValueType重载了System.Object的Equals方法,用于实现对实例数据的判等。
ReferenceEquals,对值类型应用ReferenceEquals将永远返回false。
==,未重载的==的值类型,将比较两个值是否“按位”相等。
4.2 引用类型判等
Equals,主要有两种方法,如下 public virtual bool Equals(object obj); public static bool Equals(object objA, object objB);
一种是虚方法,默认为引用地址比较;而静态方法,如果objA是与objB相同的实例,或者如果两者均为空引用,或者如果objA.Equals(objB)返回true,则为true;否则为false。.NET的大部分类都重写了Equals方法,因此判等的返回值要根据具体的重写情况决定。
ReferenceEquals,静态方法,只能用于引用类型,用于比较两个实例对象是否指向同一引用地址。
==,默认为引用地址比较,通常进行实现了==的重载,未重载==的引用类型将比较两个对象是否引用地址,等同于引用类型的Equals方法。因此,很多的.NET类实现了对==操作符的重载,例如System.String的==操作符就是比较两个字符串是否相同。而==和equals方法的主要区别,在于多态表现上,==是被重载,而Equals是重写。
有必要在自定义的类型中,实现对Equals和==的重写或者重载,以提高性能和针对性分析。
5. 再论类型转换
类型转换是引起系统异常一个重要的因素之一,因此在有必要在这个主题里做以简单的总结,我们不力求照顾全面,但是追去提纲挈领。常见的类型转换包括:
隐式转换:由低级类型项高级类型的转换过程。主要包括:值类型的隐式转换,主要是数值类型等基本类型的隐式转换;引用类型的隐式转换,主要是派生类向基类的转换;值类型和引用类型的隐士转换,主要指装箱和拆箱转换。
显示转换:也叫强制类型转换。但是转换过程不能保证数据的完整性,可能引起一定的精度损失或者引起不可知的异常发生。转换的格式为, (type)(变量、表达式)
例如:int a = (int)(b + 2.02);
值类型与引用类型的装箱与拆箱是.NET中最重要的类型转换,不恰当的转换操作会引起性能的极大损耗,因此我们将以专门的主题来讨论。
以is和as操作符进行类型的安全转换,详见本人拙作《第一回:恩怨情仇:is和as》。
System.Convert类定义了完成基本类型转换的便捷实现。
除了string以外的其他类型都有Parse方法,用于将字符串类型转换为对应的基本类型;
使用explicit或者implicit进行用户自定义类型转换,主要给用户提高自定义的类型转换实现方式,以实现更有目的的转换操作,转换格式为,
static 访问修饰操作符 转换修饰操作符 operator 类型(参数列表);
垃圾回收的简单阐释:
//实例定义及初始化 MyClass mc1 = new MyClass(); //声明但不实体化 MyClass mc2; //拷贝引用,mc2和mc1指向同一托管地址 mc2 = mc1;
//定义另一实例,并完成初始化 MyClass mc3 = new MyClass(); //引用拷贝,mc1、mc2指向了新的托管地址 //那么原来的地址成为GC回收的对象,在 mc1 = mc3; mc2 = mc3; #endregion
}
GC执行时,会遍历所有的托管堆对象,按照一定的递归遍历算法找出所有的可达对象和不可访问对象,显然本示例中的托管堆A对象没有被任何引用访问,属于不可访问对象,将被列入执行垃圾收集的目标。
参数基础论:
简单的来说,参数实现了不同方法间的数据传递,也就是信息交换。Thinking in Java的作者有过一句名言:一切皆为对象。在.NET语言中也是如此,一切数据都最终抽象于类中封装,因此参数一般用于方法间的数据传递。例如典型的Main入口函数就有一个string数组参数,args是函数命令行参数。通常参数按照调用方式可以分为:形参和实参。形参就是被调用方法的参数,而实参就是调用方法的参数。例如: using System; public class Arguments { public static void Main(string [] args) { string myString = "This is your argument."; //myString是实际参数 ShowString(myString); } private void ShowString(string astr) { Console.WriteLine(astr); } }
由上例可以得出以下几个关于参数的基本语法:
形参和实参必须类型、个数与顺序对应匹配;
参数可以为空;
解析Main(string [] args),Main函数的参数可以为空,也可以为string数组类,其作用是接受命令行参数,例如在命令行下运行程序时,args提供了输入命令行参数的入口。
另外,值得一提的是,虽然CLR支持参数默认值,但是C#中却不能设置参数默认值,这一点让我很郁闷,不知为何?不过可以通过重载来变相实现
泛型类型参数:
泛型类型参数,可以是静态的,例如MyGeneric<int>;也可以是动态的,此时它其实就是一个占位符,例如MyGeneric<T>中的T可以是任何类型的变量,在运行期动态替换为相应的类型参数。泛型类型参数一般也以T开头来命名。
可变数目参数:
一般来说参数个数都是固定的,定义为集群类型的参数可以实现可变数目参数的目的,但是.NET提供了更灵活的机制来实现可变数目参数,这就是使用param修饰符。可变数目参数的好处就是在某些情况下可以方便的提供对于参数个数不确定情况的实现,例如计算任意数字的加权和,连接任意字符串为一个字符串等。我们以一个简单的示例来展开对这个问题的论述,
param关键字的实质是:param是定制特性ParamArrayAttribute的缩写(关于定制特性的详细论述请参见第三回:历史纠葛:特性和属性),该特性用于指示编译器的执行过程大概可以简化为:编译器检查到方法调用时,首先调用不包含ParamArrayAttribute特性的方法,如果存在这种方法就施行调用,如果不存在才调用包含ParamArrayAttribute特性的方法,同时应用方法中的元素来填充一个数组,同时将该数组作为参数传入调用的方法体。总之就是param就是提示编译器实现对参数进行数组封装,将可变数目的控制由编译器来完成,我们可以很方便的从上述示例中得到启示。例如:
static void ShowAgeSum(string team, params int[] ages){...}
实质上是这样子:
static void ShowAgeSum(string team, [ParamArrayAttribute] int[] ages){...}
param修饰的参数必须为一维数组,事实上通常就是以群集方式来实现多个或者任意多个参数的控制的,所以数组是最简单的选择;
param修饰的参数数组,可是是任何类型。因此,如果需要接受任何类型的参数时,只要设置数组类型为object即可;
param必须在参数列表的最后一个,并且只能使用一次。
参数传递:值类型传递、引用类型传递
参数传递根据参数类型分为按值传递和按引用传递。
值类型传递:(值类型参数的按值传递和引用类型参数的按值传递)
1、默认情况下都是按值传递的。按值传递主要包括值类型参数的按值传递和引用类型参数的按值传递。值类型实例传递的是该值类型实例的一个拷贝,因此被调用方法操作的是属于自己本身的实例拷贝,因此不影响原来调用方法中的实例值。
2、引用类型参数的按值传递
当传递的参数为引用类型时,传递和操作的是指向对象的引用,这意味着方法操作可以改变原来的对象,但是值得思考的是该引用或者说指针本身还是按值传递的。
我们从基本的理解入手来了解引用类型参数按值传递的本质所在,简单的说对象作为参数传递时,执行的是对对象地址的拷贝,操作的是该拷贝地址。这在本质上和值类型参数按值传递是相同的,都是按值传递。不同的是值类型的“值”为类型实例,而引用类型的“值”为引用地址。因此,如果参数为引用类型时,在调用方代码中,可以改变引用的指向, 从而使得原对象的指向发生改。
按值传递的实质的是传递值,不同的是这个值在值类型和引用类型的表现是不同的:参数为值类型时,“值”为实例本身,因此传递的是实例拷贝,不会对原来的实例产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向
class Program { static void Main(string[] args) { Myclass my = new Myclass(); Console.WriteLine(my.str);//方法调用之前 输出:我是Myclass Change(my);//方法中 输出:我在Change方法中 Console.WriteLine(my.str);//方法调用之后 输出:我在Change方法中,原来的值被改变了。 Console.ReadKey(); } public static void Change(Myclass mc) { mc.str = "我在Change方法中";//如果参数为引用类型时,在调用方代码中,可以改变引用的指向, 从而使得原对象的指向发生改变 Console.WriteLine(mc.str); } } public class Myclass { public string str = "我是Myclass"; }
引用类型参数的按值传递和按引用传递的区别:
引用类型参数的按值传递,传递的是参数本身的值,也就是上面提到的对象的引用;
按引用传递,传递的不是参数本身的值,而是参数的地址。如果参数为值类型,则传递的是该值类型的地址;如果参数为引用类型,则传递的是对象引用的地址。
值类型传递:按值传递的实质的是传递值,不同的是这个值在值类型和引用类型的表现是不同的:参数为值类型时,“值”为实例本身,因此传递的是实例拷贝,不会对原来的实例产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向
当你无法控制自己的情绪
将时间一分一秒地花在随大流、追热点、逞能斗气、不干实事
人生就会像一架坏掉的机器,创造不出优质的产品