数据类型
具有某种数据类型的数据,称为这个数据类型的实例。如学生是类型,王小二是实例。
类型可以拥有自己的成员(属性和方法等)。 类型type指的是类、结构、接口、委托、枚举。
C#的类型包括值类型和引用类型,其中重要的类型定义成基元类型。
C#的预定义类型:8种整数型,2种2进质浮点型,1种金融十进制浮点型,1种布尔型,1种字符型。
- 值类型 堆
int long char double datetime 属于结构 枚举emnu
- 引用类型 栈
string class 接口 委托
引用类型只可以为null,值类型不可以
string str=null;
string str1='//n';//转义
类实际对象的计算机语言表示,拥有许多成员,最重要的是属性和方法。
对象是类的实例
属性存储信息和数据
方法是供我们可用的函数
运算符和分支结构
- 运算符
//一元运算符
+ - ++ --
int i=i++ i=i-- //放后是执行完再加一
//二元运算符
+ - * / %
== != < > <= >= //关系运算
&& ||
+= -= *= /= %=
//三元运算符
? :
//运算符重载
//分支结构
if else
switch case
方法与参数
方法method
用来把一些相关的语句收集在一起的代码块。方法必须有方法名,拥有0个或多个参数,可以有0或1个输出,C#7后可以有多个输出,在这之前借助Out关键字实现多输出。
一般定义不返回函数值得叫方法,返回值的叫函数。
C#所有数据结构:类 结构 接口,可以有方法。 枚举 不可以写方法,委托的方法是固定的不可以自定义
方法的定义/签名signature
- 修饰词 public protected internal private
- 方法的返回类型。void
- 方法名称
- 方法参数
- 实际参数和形式参数
- 按值传递和按引用传递ref
- out关键字的使用
- 可变关键字 params 数组传参 必须在最后
//定义
public int a(params int[] b){
return 0;
}
//调用
a(1,2,3)//可变长度
- 可选参数
- 命名参数
- 递归调用recursion
数组与循环
- Array a =new int[10]
- 索引 从0开始 索引循环的类型是一致的
- 一维数组
- 二维数组 交错/齿轮数组
- 循环 for foreach
- while和do while
面向对象和类
- 面向对象 object oriented
- 封装集成多态
- 类里包括:
常量const、字段field、属性property、构造函数、析构函数、自定义方法、嵌套类 - 构造函数 construct
当使用new关键字时,自动调用构造函数,构造函数名称与类相同,public,无返回值。构造函数可以是private(单例模式)
public class car{
public colors color{
get{
return this;
}
//set打开后,get也要打开
};
set{
this=value;//默认value
};
}
}
- 对象初始化器
- 静态成员
静态方法只能使用静态字段和属性。
静态成员无需实例化即可使用。
静态成员属于类而非类的实例,但被所有类共享。
实例的方法可以使用静态和实例字段和属性。 - 静态构造函数
当你的类中有静态函数,C#默认会创建一个静态构造函数,为你的静态成员赋值,不能有修饰词,不能有输入和输出。静态构造函数只调用一次。
结构
- 结构是值类型,可以被看作是轻量级的类
- 设计结构是为了提升程序的性能:
引用用类型内存的初始化需要牵扯到堆上内存的分配,因此如果一个自定义的类型==只会包括值类型作为成员==,使用结构是一个较好的选择 - C#自带的结构有很多,包括int,DateTime等,它们的成员==全部是值类型==。
- 结构可以有自己的方法
- 结构的成员最好是值类型
继承inheritance
- 所有类继承于System.Object Get.Type(反射)
- private 只用自己能访问
- protected 自已和子类
- this当前类型,可以用this呼叫其它构造函数,Base调用基类构造函数
- 实例化对象会一层一层,执行到System.Object基类
- 方法的重写override、重载overload、隐藏
- 方法隐藏,子类和父类方法名称相同,自动隐藏,子类可以用new关键字
- 方法重写,子类和父类拥有相同的签名方法,但子类希望得到不同的行为,而且父类的方法是虚方法或抽象方法。
- 方法重载,子类和父类有相同的方法名称,但签名不同。
- 密封类sealed:
- sealed修饰类型,不能补继承
- sealed修饰方法,不能被重写
- 结构是密封类型,不能被继承
- 抽象类abstract:必须被继承的类
- 无法实例化一个abstract,子类必须用override来重写它
- 不能与sealed修饰
- 比较虚的概念使用abstract
- abstract可以有普通的方法,也可以有抽象方法
- 接口-特殊的抽象类,只能有抽象方法
- 抽象方法
- 抽象方法本身没有方法体,待子类去实现
- 抽象方法必须在抽象类中
- 虚方法virtual
- virtual可以有方法实体,也可以没有
- 子类可以重写,也可以不重写
- 用于父类可以实现,但子类有不同实现方式的情景
public class a{
int x;
int y;
public a(){}
public a(int x,int y){
this.x=x;
this.y=y;
}
}
public class b:a{
public b(int x,int y):base(x,y){}
}
多态和接口
- 多态 polymorphims
- 一个类型有多种状态。
- 接口 interface
- 接口是一种特殊的类,方法只能有签名,而不能有方法体。
- 使用借口实现has-a关系。
- 使用接口实现多重继承,解决单一继承的缺陷。
- 接口就像技能,通过多重继承赋予不同的技能。
- 策略模式
- ==依赖注入==
第三部分 数据结构
- C# 2.0 泛型
- C# 3.0 linq
- C# 4.0 动态语言
集和与泛型
- 数据结构 Data Sturcture
- 数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。
- 简单分类:线性表,链表,哈希表,树,图等
- 大部分编程语言都可以实现这些数据结构,C#已经为我们实现了一部分,例如线性表(Array,List,ArrayList,Stack,Queue),链表(LinkedList),哈希表(HashTable,Dictionary)等等
- C#中的集合对应了一些数据结构
- 集合 collection
- 包括泛型集合与非泛型集合,它们都是不定长的
- 非泛型集合:集合里的元素可以为任意的类型
- 泛型集合:集合里的元素只可以为一种类型T
- 从定义上,数组(Array)不是集合因为它是定长的
- 非泛型集合
- ArrayList
- Stack 栈
- 后进先出(LIFO)
- 栈是限制插入和删除只能在一个位置上进行的线性表,该位置是表的末端,叫作栈顶,对栈的基本操作有push(进栈)和pop(出栈),前者相当于插入,后者相当于删除最后一个元素
- 练习:栈中全是int,找出栈中最小的元素
- Queue 队列
- 先进先出(FIFO)
- 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
- 装箱boxing与拆箱unboxing
-非泛型集合的两个问题:造成装箱和拆箱\类型不安全- 装箱的定义:将值类型分配给object变量
- 拆箱的定义:将object变量中的值赋给一个值类型
- 拆箱有风险,操作需谨慎
- 泛型集合
- 泛型集合List<T>
- T称为类型参数或占位符
- 实际使用时必须为T指定一个具体类型,例如int,string等,称为实际参数
- 栈和队列的泛型版本
- 由于性能和类型安全,出现泛型后以经没有人使用非泛型合集
- 泛型方法
- 泛型方法:输入输出参数包括了类型参数
- 例如:交换任意两个同类型变量的值
- 使用泛型方法可以达到代码复用的目的
- 泛型类型
- 可以自定义泛型结构和类
- 例如,创建一个自定义的泛型结构
- 泛型约束
- 泛型约束使得类型参数T只能是满足某种条件的类型
- 接口约束:使得泛型的实际参数必须实现某个接口
- 基类型约束:实参必须是某个类的派生类
- 构造函数约束:泛型实参必须具有可访问的无参构造函数(默认的也可)
public a<T,K>(T b,K c) where T: new() where K:struct
- 可空类型
- 定义为Nullable<T> 或者用?
- 一个结构,使用HasValue()判断是否有值
算法介绍
- 算法 algorithm
- 定义 有限步操作的集合
- 有线性、确切性、输入、输出、可行性
- 时间复杂度O(S)和空间复杂度n(S)
- 常用数据结构
- 线性列表
- array 定长 类型安全
- arrayList 非泛型 数据类型不安全
- List<T> 泛型
- 链表
- 定义:包括节点和下一个借点的引用。在内存中不连续。
- 双向链表则还包括前一个节点的引用。
- 好处:节省内存空间,中间插入和删除的时间复杂度底。(常数复杂度,线性列表为线性)
- 坏处:不能按索引器访问
-哈希表 hashtable
-哈希表是键值对数据结构key value
-哈希函数和哈希碰撞(解决哈希碰撞,线性寻址法,平方寻址法) - 插入和删除的时间复杂度都是常数,但哈希表不能排序。
- 节省空间,速度快
- hashtable非泛型
- Dictionary<K,T>
- 线性列表
CLR(公共语言运行时)
- .net框架主要成员
- 两部编译和跨平台
-中间语言IL,c#通过csc.EXE,先编译为中间语言,在运行时,CLR通过JIT即使编译为机器码。
委托和事件
-
委托 delegate
- 委托是一个特殊的类,是密封袋,不可以继承。包括一个特殊的方法invoke,也可以看成一个签名。
- 使用委托传入方法,起到代码复用的目的
- 可以为委托提供特殊的签名方法
-
委托的原理:
- 委托是一个密封类,它继承自System.MulticastDelegate,后者再继承自System.Delegate
这个类的成员是固定的,这个密封类包括一个构造函数和三个核心函数,Invoke方法赋予其同步访问的能力,BeginInvoke,EndInvoke赋予其异步访问的能力 - 委托链:
- System.MulticastDelegate是所有委托的父类,它含有一个非常重要的字段_invocationList,指的是委托自身的方法链,它是一个Delegate类型的数组,所以委托可以挂接多于一个函数(即一个函数List),可以为这个链自由的添加或删除函数。
- 一个委托链可以没有函数如果执行委托,将会顺序的执行委托链上所有的函数。如果某个函数出现了异常,则其后所有的函数都不会执行如果你的委托的委托链含有很多委托的话,你只会收到最后一个含有返回值的委托的返回值
- 委托链使用+=将方法链接。
- System.Delegate是System.MulticastDelegate的父类,它含有2个非常重要的字段_target和_methodPtr。如果委托指向了一个实例方法,_target将会等于该实例本身。在方法中,可以通过this访问。如果委托指向了一个静态方法,_target将会等于null无论委托指向什么方法,_methodPtr都是CLR用于标示委托指向方法的IntPtr值
- 委托的构造函数设置_invocationList,_target和_methodPtr在进行调用时,Invoke方法使用_target和_methodPtr在指定对象上调用对应的方法
- 委托是一个密封类,它继承自System.MulticastDelegate,后者再继承自System.Delegate
-
事件
- 事件必须包括三部分:
- 事件的订阅者(Subscriber),订阅者会在事件触发之后,做出响应行为。
- 事件的触发者,或者发布者(Publisher),条件得到满足时,触发事件订阅者和触发者之间的数据传送通道。
- 事件使用步骤
- 声明一个事件处理方法,指出事件发生时,订阅者应该有怎样的反应。该方法可以传入继承自EventArgs类型的自定义数据
- 声明委托。委托的签名和事件处理方法相同
- 声明基于该委托的事件
- 为事件增加订阅者
- 在事件符合条件时,调用事件
- 事件本身是私有的
- 事件会被转化为一个私有的字段,以及两个方法。字段的类型和事件关联的委托相同,而两个方法的输入类型和事件关联的委托相同,没有输出
- 事件将字段改为私有,从而令外部不能更改它的值
- 在事件所处的对象之外,事件只能出现在+=,-=的左方
- 事件必须包括三部分:
-
泛型委托
- C# 2 中提供了两个泛型委托Action和Func
- Action是没有输出的,Func可以有一个输出
- 使用它们可以代替原始的delegate关键字
委托实际上就是函数指针,将不同函数像变量一样传递,供不同参数调用。
匿名类型、var关键字和扩展方法
- 隐式类型
- 使用var关键字
- 只能对局部变量使用隐式类型
- 在foreach中一般不需要指定类型
- 匿名类型
- 匿名类型允许你直接在花括号内建立类型,而无需指定类名称
- 只能用var或object来修饰,用var修饰可以用点来获取成员
- 如果两个匿名类型有相同的成员,且成员类型相同,顺序相同,编译器会将它们做为同一个类型。
- 扩展方法
- 在不创建子类,不更改类型本身的情况下,修改类型
- 扩展方法必须定义于静态的类型中,且扩展方法必须是静态的。
- 扩展方法第一个输入参数要加上this,扩展方法必须至少有一个输入参数
//匿名类型
var s = new { name = "小李", age = 15 };
var b=s.name;
Console.WriteLine(s.GetType());//返回<>f__AnonymousType0`2[System.String,System.Int32]
//扩展方法
public static class StringExtension //1、在静态类中,2、一般用Extension做为后缀
{
public static bool isHuiwen(this string input)
{
//算法
return true;
}
}
bool a = "string".isHuiwen//调用
匿名方法和lambda表达式
- 匿名方法出现在C#2中
- 闭包:可以使用捕获变量(在匿名方法外部的变量)
- lambda表达式
- C#3为了令代码更加简化,引入了lambda表达式
- lambda表达式,是表达式的一种,它可以表式一个函数
- 基本结构分三个部分
- 输入参数 一个参数不需要括号,多个参数或没有参数需要括号
- 箭头 =>
- 表达式或语名块 如有只一条件语句不需要花括号,多行需要花括号
//old:泛型委托实现字符串判定
static void Main(string[] args)
{
var data = new List<string> { "string", "this", "the", "a", "dog" };
var func = new Func<string, bool>(Predicate);//泛型委托实现,string输入的参数,bool返回的类型
//使用匿名方法的方式,不用在单独定义Predicate这个方法
var func1 = new Func<string, bool>(delegate (string input)
{
return input.Length >= 4;
});
//lamdbe表达式写法
Func<string, bool> func2 =(input)=>{
return input.Length >= 4;
};
foreach (var word in data)
{
if (func(word))
{
Console.WriteLine(word);
}
}
Console.ReadKey();
}
public static bool Predicate(string input)//使用匿名方法就这用写这个了
{
return input.Length >= 4;
}
LinQ Language-Integrated Query语言结构化查询
-
工具准备
- linQpad
- AdventureWork数据库
-
LinQ查询操作
LinQ分为查询表达式和方法语法
-
查询表达式转换为方法语法再转化为SQL
var data = Enumerable.Range(100, 20); //LinQ查询(查询表达式) var list1 = from s in data where s > 115 select s; //LinQ查询(方法语法) var list = data.Where(a => a > 115); Console.WriteLine(String.Join(",", list1)); Console.ReadKey();
-
投影操作符
- Select 部分列(使用了匿名类型)
- SelectMany 在“父子表”中,使用SelectMany进行查询
-
过滤操作符
- Where 过滤
- Distinct 会删除序列中重复的元素
-
排序操作符
- OrderBy 升序
- OrderByDescending 降序
- ThenBy 多列排序,放在order后继续排序
- ThenByDescending 降序
-
分组操作符
- group by into
from p in Persons group p by p.Name into g select new {name=p.key,count=g.count} var a=Persons.GroupBy(p=>p.Name) .Select(g=>new{ name=g.Key, Count=g.Coutn() })
-
连接操作符
- 内连接,左外连接和右外连接
- 在进行内连接时,必须要指明基于哪个列
var customerList = new List<Customers> { new Customers{ customerId=1,Name="张三",PhoneNumber=123,Address="中国" }, new Customers{ customerId=2,Name="李四",PhoneNumber=123,Address="中国" }, new Customers{ customerId=3,Name="王五",PhoneNumber=123,Address="中国" } }; var infoList = new List<CustomerInfo> { new CustomerInfo{ customerId=1, id=1,Number=70681 }, new CustomerInfo{ customerId=4, id=1,Number=70681 }, new CustomerInfo{ customerId=2, id=1,Number=70691 } }; //连接基于两个表,以及基于至少一列 //左联还是右联,基于哪个先写 var cus = from o in infoList join c in customerList //基于哪个列 on o.customerId equals c.customerId select new { c.Name, o.Number }; var cus1 = infoList.Join(customerList, o => o.customerId, c => c.customerId, (o, c) => new { c.Name, c.Address, o.Number }); //左外连接,左边的表中所有的数据都会出现在结果集中,右表要给默认值 var leftcus = from c in customerList join o in infoList on c.customerId equals o.customerId into p //右表默认值 from item in p.DefaultIfEmpty(new CustomerInfo { id = -1, Number=-1 }) select new { c.Name, c.Address, item.Number }; Console.WriteLine(String.Join("\n", leftcus)); Console.ReadKey();
-
分区操作符
- Take:从序列的开头返回指定数量的连续元素,相当于SQL的TOP N
- Skip:跳过序列中指定数量的元素,然后返回剩余的元素
-
集合操作符
- 当想操作两个集合时非常有用。
- Union:并集,返回两个序列的并集,去掉重复元素,相当于SQL的Union。
- Concat:并集,返回两个序列的并集,不处理重复元素,相当于SQL的Union All。
- Intersect:交集,返回两个序列中都有的元素,即交集。
- Except:差集,返回只出现在一个序列中的元素,即差集。
-
元素操作符
- First:返回序列中的第一个元素;如果是空序列,引发异常。
- FirstOrDefault:返回序列中的第一个元素;如果是空,则返回默认值default(TSource)。
- Last:返回序列的最后一个元素;如果是空序列,此方法将引发异常。
- LastOrDefault:返回序列中的最后一个元素;如果是空返回默认值default(TSource)。
- Single:返回序列的唯一元素;如果是空或多个元素,引发异常。该方法非常适合判断序列是否只包含一个元素(并返回它)。
- SingleOrDefault:返回序列中的唯一元素;如果是空,则返回默认值default(TSource);如果该序列包含多个元素,此方法将引发异常。
-
延迟执行
- 返回另外一个序列(通常为IEnumerable<T>或IQueryable<T>)的操作,会延迟执行(在最终结果的第一个元素被访问的时候才真正开始运算)
- 返回单一值的运算,会立即执行
- 强制立即执行,可以使用ToList()
反射和动态语言运行时
- dynamic关键字
- C#4引入动态类型dynamic,弱类型语言
- dynamic与var的区域
- 运行时推断
- var不能作为方法的输入和返回值类型,dynamic可以
- var不能修饰类或结构的成员,dynamic可以
- 反射
- 在.net中,查看和操作元数据的动作称为反射(Reflection)
- 通过反射加载外部程序集/类型,并调用方法
- 反射允许你访问类型任何的成员,包括私有的
//反射 var type = typeof(ReflectionTest); //获得类型所有的成员 var members = type.GetMembers(); //通过反射获得构造函数 var constructor = type.GetConstructors()[0]; //调用构造函数 object obj = constructor.Invoke(null); //通过反射调用实例方法,传入实例 var method = type.GetMethod("AddTwo"); var o = method.Invoke(obj, new object[] { 100 }); //动态语言运行时提供的晚期绑定方法 //创建一个ReflectionTest类型的实例 //让它是一个动态类型 dynamic dynamicObj = Activator.CreateInstance(typeof(ReflectionTest)); //可以使用普通的方式调用方法 //编译器不检查,不要拼错 var a = dynamicObj.AddTwo(100);
- 动态类型简化晚期绑定
- 普通的反射
- 动态类型使得方法呼叫更简单
- 动态类型简化COM互操作
- 我们可以使用C#创建并修改Word,Excel文件。这些都是基于COM
- 没有动态类型时,访问COM对象必须使用类型强制转换
- 通过动态类型简化代码
C#5
C#2泛型
C#3LinQ
C#4动态语言运行时
C#5多线程
C#6-7
C#6-2015年7月,Vs2015 .net framework4.6语法糖
C#7-2017年3月,Vs2017 .net framework4.6.2增加元组和模式匹配,更具有函数式语言特性
- 字符串插值
string name="张三";
string s="123";
//old
string.Format("{0}-{1}",name,s);
//new
$"{name}-{s}";
- 使用static using呼叫静态类方法
- 为了降低代码的冗余,我们希望在呼叫静态类的方法时,可以省去类的名称
- 判断null的简写操作符?.
- 利用新的操作符“?.”(称为nullet)可以简化代码
- 异常过滤
- 现在可以在catch后面跟when子句进一步对异常进行过滤并操作
- 对于一些来自第三方传回的带有固定异常代码的异常非常有用
- [C# 7]改进的out关键字
- 现在,out关键字是即插即用的
- 允许以_(下划线)形式“舍弃”某个out 参数
- [C# 7]值类型元组(ValueTuple)
- 元组(Tuple)是一个任意类型变量的集合,并最多支持八个变量
- C# 4中元组最大的两个问题是:
- Tuple类将其属性命名为 Item1、Item2等,这些名称是无法改变的,因此只会让代码可读性变差
- Tuple类是引用类型,使用任一Tuple类型即意味着在堆上分配对象,因此,会对性能有负面影响,还不如辛苦一点手写一个结构体
- C# 7引入的新元组(ValueTuple),解决了上面两个问题,它是一个结构体,并且,你可以传入描述性名称(TupleElementNames属性)以便之后更容易的调用它们
- 作为方法的参数和返回值,返回多个变量