搬家到博客园了,博客园地址
VS快捷键
- F12 跳转到定义处。
- Ctrl+“-” 跳回原处。
- F6 编译。
- F5 debug。
- F11 单步debug运行。
- F10 跨步debug运行。
- 两下tab键,填充目标语句。
- cw填充为Console.WritelLine();
- 自定义构造器(构造函数)“ctor”
- 类成员"prop"
- svm为Main函数
- 在代码上面输入"///"会自动补充注释头
- Ctrl+K+C 上注释,+U解注释
- Ctrl+M+O 折叠所有代码,+L展开所有代码,+M折叠当前代码
一、预备节
-
@
解除转义:@"C:\Windows"
="C:\\Windows"
将关键字作为标识符使用:int @int = 1;
字符串跨行:string str = "line one" + "line two" + "line three" + "line fore"; string str = @"line one line two line three line fore";
$
将字符串转化为“内插字符串”:
输出的变量不需要再用索引输出,直接外加"{}"写在字符串中即可。 -
结构为:
namespace 声明
class声明
class属性与成员声明
Console.ReadLine()
只接受字符串数据。ref int x
中的ref
为引用声明,放在数据类型前面,引用参数是一个对变量的内存位置的引用。foreach (类型 元素 in 数组)
每次将数组的一个赋值给元素。- 可空类型(Nullable):
类型? 变量名 = null;
在现有的数据类型范围基础上加个null。 a ?? b
: a如果为null则返回b。- 日期格式化,见笔记
dynamic
为自动数据类型,根据赋值的不同自动变。typeof(x)
中的x,必须是具体的类名、类型名称等,不可以是变量名称。
.GetType()
方法继承自Object,所以C#中任何对象都具有GetType()方法。- 数据类型分为:值类型 & 引用类型 & 指针类型
引用类型的变量在栈中分配,引用类型的实例在堆中分配。
值类型总是分配在它声明的地方:作为类的字段(成员变量)时,跟随其所属的实例存储,也就是存储在了堆中;作为方法中的局部变量时,存储在栈上。
数组本身是引用类型,但是不管数组存储的元素是值类型还是引用类型,都是存储在堆中。
详见此博客
- 重载:
只要参数列表不同就行,返回值可同可不同!因为仅返回值不同的时候甚至不能通过编译,调用函数时不知道该调用那个,违反了上下文独立性。
区分于“ 重写 (覆盖)”:存在于父子类间的关系。
-
读数据:
Console.Read()
控制台读一个字符
Console.ReadLine
控制台读一行字符串
c#中键盘录入结果是转换成string类型的,所以输出结果需要转换成相应的数据类型!
形式为:
数据类型.parse(Console.ReadLine());
或者使用:
Convert.ToXXX(str);
-
隐式转换:
数往大装,类往小装。
-
字符串拼接
string str = "my name is" + name + "my age is" + age; string str = string.Format("my name is {0},my age is {1}.", name, age); string str = $"my name is {name},my age is {age}.";
二、数组
2.1. 声明并初始化:
int [] array = new int[3];
int [] array = {1,2,3};
int [] array = new int[3]{1,2,3}; //同上
int [] array = new int[]{1,2,3}; //同上
2.2. 数组赋值(别名):
int [] marks = new int[] { 99, 98, 92, 97, 95};
int[] score = marks;
//score 与 marks指向同一内存地址。
2.3. 多维数组(矩阵):
int [,] a = new int [3,4] { //二维数组
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
int val = a[2,3]; //二维数组访问(第3行第4个)
2.4. 交错数组(数组嵌套):
//92 93 94
//85 66 87 88
int[][] scores = new int[2][]{new int[]{92,93,94},new int[]{85,66,87,88}};
/*
scores 是一个由两个整型数组组成的数组
scores[0] 是一个带有 3 个整数的数组,
scores[1] 是一个带有 4 个整数的数组。
*/
2.5. 传递数组给函数
通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
double getAverage(int[] arr);//1维
double getAverage(int[,] arr)//2维
int getArea(int[][] array);//交错数组
2.6. 参数数组
有时,当声明一个方法时,您不能确定要传递给函数作为参数的参数数目。
C# 提供了 params 关键字,使调用数组为形参的方法时,既可以传递数组实参,也可以传递一组数组元素。
- 带 params 关键字的参数类型必须是一维数组,不能使用在多维数组上
- 不允许和 ref、out 同时使用
- 带 params 关键字的参数必须是最后一个参数,并且在方法声明中只允许一个 params 关键字
- 不能仅使用 params 来使用重载方法
- 没有 params 关键字的方法的优先级高于带有params关键字的方法的优先级
public int AddElements(params int[] arr)
app.AddElements(512, 720, 250, 567, 889);
2.7 Array类
三、类与接口
3.0 字段、属性与方法
字段:类似C++中的成员属性,通常设置为private,并进行封装。
private string name;
属性:分为 “有参属性” 和 “无参属性”。
//(1)原始写法--get、set访问器
public string Name
{
get{return name;}
set{name = value;}
}
//(2)另一种写法
public string Name
{
get => name;
set => name = value;
}
//(3)简略写法
public string Name { get; set;} = 初值
方法:类似C++中的成员方法
3.1. static静态 (上升至类级别)
static 关键字把类成员定义为静态的(静态变量、静态函数),意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
静态函数 在对象被创建之前就已经存在,甚至不用实例化就可以使用,但是只能访问静态变量。
static修饰的成员必须通过类来访问。
3.2. 类的定义及实例化
class Student
{
public int ID{get;set;}
}
static void Main(string[] args)
{
Student stu = new Student(){ID = 1};
}
- 自定义构造器(构造函数)“ctor”:
- 类成员"prop":
3.3. 类的访问级别
internal
:仅限 “项目” 内的访问。<默认>public
:开放。<接口的默认>abstract
:仅限 “项目” 内的访问,该类不能被实例化,只能被继承。public abstract
:开放,该类不能被实例化,只能被继承。sealed
:仅限 “项目” 内的访问,该类不可被继承。public sealed
:开放,该类不可被继承。
3.4. 继承
-
只能有1个基类,但可以实现多个基接口。(只能有一个亲爹,可能有多个义父)
-
加上
sealed
限定符的表示 “封闭类” ,不可被继承。 -
基类与派生类:
class 基类 { ... } class 派生类 : 基类 { ... }
-
this
&base
访问关键字:
this
:指代引用类的当前实例。
base
:从派生类中访问基类的成员:
详见此博客 -
子类的构造:
访问修饰符 派生类(参数列表) : base(参数列表) { ... } //如果父类已经对成员进行赋值了,此构造器内就不需要再赋值一次。
-
用子类来实例化父类:(多态基础)
父类 实例名 = new 子类();
3.5. 重写与多态(虚方法)
-
多态:子类需要覆写父类中的方法来实现子类特有的行为。
-
虚方法: 使用
virtual
和override
关键字实现方法重写:class Animal { public virtual void Run() //父类实例调用Run时,使用父类(跟实例走) { } } class Cat:Animal { public override void Run() //子类实例调用Run时,使用子类 { } }
-
隐藏基类成员:
相当于子类将父类方法继承下来,但是被本身的方法隐藏。class Animal { public void Run() //由对象类型决定, { } } class Cat:Animal { public new void Run() // new也可以不写,但是会警报 { } }
-
虚方法中必须有实现部分。
3.6 抽象类与接口
为了解决多态时基类函数方法模糊、无用的问题,可以将该方法定义为抽象方法,此方法所在的类也必须是抽象类,抽象类不能被实例化,可以有普通方法、虚方法、抽象方法,但是抽象方法只能出现在抽象类中。
-
抽象方法:与上述虚方法类似,此时用的是
abstract
与override
搭配使用,不提供实现部分,由派生类强制重写, -
接口:如果是“纯虚类”(只写规范),则可以将抽象类再抽象为接口,降低耦合度。适用于多继承
interface 接口名 { int 属性名; void 方法名(); }
3.7 扩展方法
如果要增加类中的功能 :
1、如果有源码并可以修改类内容,直接加
2、如果不能修改类(比如在一个第三方类库中),只要不是sealed,可以通过派生类增加。
3!、如果不能访问代码 & 这个类是sealed & 有其他设计原因这些方法不适用,就不得不在另一个使用该类的public成员的类中编写拓展方法。
static class ExtendMyAverage //简易扩展,但也就是另一个类使用了MySum类实例当参数
{
public static double Average(MySum ms) //MySum 类的实例
{
return ms.Sum()/3; //使用实例中的public方法
}
}
//方法调用:
ExtendMyAverage.Average(ms);
如果可以用原来的类自身调用拓展方法将更加符合认知:(就像原来的类自己扩展了方法)
- 新建类必须是static;
- 拓展方法必须是public static的,第一参数必须包含“this 原本类 类的实例”;
static class ExtendMyAverage //第二种方法,类必须是static的,也可以拓展接口
{
public static double Average(this MySum ms) //MySum 类的实例,必须是public static的,参数必须包含“this 原本类 类的实例”
{
return ms.Sum()/3; //使用实例中的public方法
}
}
//方法调用:就像自己的方法一样。
ms.Average();
3.8 反射与依赖注入
3.9 嵌套类(内部类)
“类种类”
public class Container //外部类
{
public class Nested //嵌套类
{
Nested() { }
}
}
3.9.1 内类访问外类
内部类可以访问外部类的所有成员(包括 private 和 protected)
但外部类如果想访问内部类则有限制
public class Container
{
public class Nested
{
private Container parent; //外部类类型
public Nested() //D1构造函数
{
}
public Nested(Container parent) //D2构造函数
{
this.parent = parent;
}
}
}
3.9.2 内部类的实例化
Container.Nested nest = new Container.Nested();
四、委托
也是一种类。
类似于C++的回调函数(该函数名会被当做参数传给其他函数,从而被别的函数调用)
4.1. Func & Action 委托
-
Action委托 只能指向返回值为空的函数。
Action 委托名 = new Action(对象.方法名); 委托名.Invoke(); //间接调用类的方法。 委托名(); //效果同上。(函数指针式)
-
Func委托可以指向有参数有返回值的函数。
Func<参数类型,返回值类型> 委托名 = new Func<返回值类型,参数类型>(对象.方法名); XX = 委托名.Invoke(参数); //间接调用类的方法。 XX = 委托名(参数); //效果同上。(函数指针式)
4.2 自定义delegate 委托
4.2.1 声明自定义委托类型
无需在类的内部声明,因为它是类型声明。
public delegate 返回类型 委托类名(参数签名);
4.2.2 创建委托对象
方法可以被委托包装的限制:
- 签名(参数列表)必须与委托完全一致
- 返回值类型必须与委托一致
委托类名 委托名 = new 委托类名(对象.方法名); //使用 new 创建对象
委托类名 委托名 = 对象.方法名; //效果同上,隐式创建委托对象。(常用)
//这种快捷语法可以工作是因为在方法名称和其相应的委托类型之间存在隐式转换。
由于委托是引用类型,类似string,具有不变性,给委托赋值时相当于创建了个新的委托。
4.2.3 委托组合(多播委托)
委托名3 = 委托名1 + 委托名2;
但“委托3”并不会影响“委托1 & 2”.
4.2.4 委托方法添加 & 移除
类似Queue,先进先出。委托不可变,+=、-=实际上是创建了新委托。
委托名 += 对象.方法名; //添加
委托名 -= 对象.方法名; //移除(从队尾搜索)
如果调用列表为空,则委托为null。
4.2.5 委托调用
XX = 委托名.Invoke(参数); //间接调用类的方法。
XX = 委托名(参数); //效果同上。(函数指针式)
如果委托有返回值:
- 调用列表中最后一个方法返回的值就是委托调用返回的值。
- 调用列表中所有其他方法的返回值都会被忽略。
4.3 匿名方法
C# 2.0引入。
委托类名 委托名 = delegate(参数列表){
方法体;
};
匿名方法不会显式声明返回值,方法内代码本身行为返回一个类型 = 委托的返回类型
4.4 Lambda表达式(也是匿名方法)
C# 3.0为了简化匿名方法的语法而引入。
MyDel del = delegate(int x) {return x+1;}; //匿名方法
MyDel del = (int x)=>{return x+1;}; //Lambda表达式
MyDel del = (x)=>{return x+1;}; //同上简易Lambda表达式
MyDel del = x => x+1; //一个参数,括号可省略
MyDel del = ()=>x+1; //没有参数必须加括号
更简化的写法与限制条件见书《C#图解教程 5th》
五、事件
5.1 含义
因为事件隶属于某个主体。
所以为一种类的成员,这种成员使类或对象具备了通知能力(手机通过响铃使手机具备了通知能力)
防止委托滥用造成程序混乱(只能+=、-=)
本质上是一个委托的包装器,封装了委托。
5.2 五个组成部分
- 发布者(Publisher):发布某个事件的类或者结构。
- 订阅者(Subscriber):注册并在事件发生时得到通知的类或者结构。
- 事件(Event):如上述介绍,被触发才会发挥功能。
- 事件处理器(Event Handler):S注册到事件的方法,在P触发事件时执行。
- 事件注册:把事件与事件处理器关联在一起
5.3 事件作用范围与功能
- 在类内,视为普通委托使用
- 在类外,只能使用+=、-=操作。
5.4 事件声明
- 完整声明:
private XXXEventHandler 委托对象; //用于声明事件的委托对象,含义见下。 public event XXXEventHandler 事件对象 { add { this.委托对象 += value; } remove { this.委托对象 -= value; } }
- 简略声明:
此时编译器会自动生成一个隐藏的委托类型字段。public event XXXEventHandler 事件对象;
5.5 事件订阅
常需要声明一个委托来承接这个事件
class XXXEventArgs:EventArgs
{
属性;
}
public delegate 返回类型 XXXEventHandler(Publisher类 p对象,XXXEventArgs e); //专门用于事件中使用的委托
或者直接不声明,使用自带的EventHandler(object,EventArgs),并在事件处理程序中先类型转换,在使用参数。
类.事件 += 方法名; //直接法
类.事件 += new 委托名(方法名); // 委托形式
类.事件 += delegate{方法体}; //匿名方法
类.事件 += () => {方法体}; //Lambda
5.6 事件触发
在触发事件之前与null进行比较,从而查看事件是否包含事件处理程序。
- 完整版:
if(XXXEventHandler != null) XXXEventHandler(参数表); //事件触发 XXXEventHandler.Invoke(参数表); //或者
- 简略版:
if(事件 != null) 事件(参数表) //事件触发
六、泛型
类型的模板
6.1 List集合
可变长度的固定类型集合,代替长度不可变的数组。
List<数据类型> my_list = new List<数据类型>();
my_list.Add(数据);
6.2 Dictionary键值对字典
Dictionary<int,string> dictionary = new Dictionary<int,string>();
6.3 泛型类
//泛型类声明:
class MyClass<T1,T2>
{
public T1 XXX;
public T2 YYY;
}
//类实例化
MyClass<int> demo = new MyClass<int>();
6.4 where语句(泛型约束)
class MyClass<T1,T2,T3> //用where来约束类型参数,多个约束的话用","分隔
where T2:Customer //T2只有Customer类型的类或者派生类才能用作类型实参
where T3:IComparable //T3只有实现IComparable接口的类才能用作类型实参
{
...
}
6.5 泛型委托
delegate TR 委托<T,TR>(T t);
6.6 泛型接口
interface IMyIfc<T>
{
T Func(T t);
}
实现接口时必须保证不会出现两个重复的接口:
继承自 IMyIfc< int > 和 IMyIfc< S >时,S可能时int,错误。
6.7 协变out、逆变in
父类是可以用子类来实例化的:class 父 = new 子();
但在下面的例子中是不正确的。
针对泛型接口和泛型委托来说的。
People people = new Teacher(); //正确,因为Teacher继承自People
List<People> peoples = new List<Teacher>(); //错误,因为没有List<People> --> List<Teacher>的继承
6.7.1 协变
七、LINQ
Language Integerated Query — 语言集成查询
八、反射和特征
8.1 接口隔离原则
“胖”接口会导致调用它的类有一些总也用不到的方法需要去实现。
为了解决这个问题需要将接口“拆分”。
最后原本的接口继承这些子接口即可。
有一些接口不想让用户显示知道并使用
显示实现接口
class WarmKiller:IGentleman,IKiller
{
public void Love(){}
void IKiller.Kill(){} //显示实现:此时必须得是IKiller实例才可以使用Kill方法。
}
8.2 反射
.NET框架特有
以不变应万变
有时程序需要在 运行时 根据用户需要处理一些逻辑,这些逻辑很难在编写程序时全面地写出来,即使写出来了,程序体也会十分臃肿、难维护。