C#基础

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折叠当前代码

一、预备节

  1. @解除转义:@"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";
    

    $将字符串转化为“内插字符串”:
    输出的变量不需要再用索引输出,直接外加{}写在字符串中即可。

  2. C#代码结构为

    namespace 声明
      class声明
        class属性与成员声明

  3. Console.ReadLine()只接受字符串数据。

  4. ref int x中的 ref为引用声明,放在数据类型前面,引用参数是一个对变量的内存位置的引用
    形参实参会应用到

  5. foreach (类型 元素 in 数组) 每次将数组的一个赋值给元素。

  6. 操作符
    int? a 可空类型修饰符:表示在int的基础上补充了为null的情况;
    a ?? b NULL合并运算符:a为null则返回b,否则返回a本身;
    a ??= b NULL合并运算符:a为null则赋值为b,否则使用a本身;
    a ?.x NULL检查运算符:a计算结果为null,则整体结果为null;如果计算结果非null,则为a.x;
    array?[x] NULL检查运算符:array计算结果为null,则整体结果为null;如果计算结果非null,则为array[x];

  7. 日期格式化,见笔记

  8. dynamic为自动数据类型,根据赋值的不同自动变;

  9. typeof(x) 中的x,必须是具体的类名、类型名称等,不可以是变量名称;
    .GetType()方法继承自Object,所以C#中任何对象都具有GetType()方法;
    二者都返回对应的Type类型对象;

  10. nameof(HumanDemo) 输出结果为 "HumanDemo":将类名等名称转变为字符串型;

  11. 数据类型分为:值类型 & 引用类型 & 指针类型

    引用类型变量中分配;
    引用类型的实例中分配。

    值类型总是分配在它声明的地方
    作为类的字段(成员变量)时,跟随其所属的实例存储,也就是存储在了堆中;
    作为方法中的局部变量时,存储在栈上。

    数组本身是引用类型,但是不管数组存储的元素是值类型还是引用类型,都是存储在中。
    详见此博客

  12. 重载:

    只要参数列表不同就行,返回值可同可不同!因为仅返回值不同的时候甚至不能通过编译,调用函数时不知道该调用那个,违反了上下文独立性。
    区分于“ 重写 (覆盖)”:存在于父子类间的关系。

  13. 读数据:
    Console.Read() 控制台读一个字符
    Console.ReadLine 控制台读一行字符串
    c#中键盘录入结果是转换成string类型的,所以输出结果需要转换成相应的数据类型!
    形式为:
    数据类型.parse(Console.ReadLine());
    或者使用:
    Convert.ToXXX(str);

  14. 隐式转换:

    数往大装,类往小装。
    "地方得够用"

  15. 字符串拼接

    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}.";
    
  16. 格式化输出:
    格式说明符语法:

    {index,对齐字段宽度:格式}
    e.g. {0,10:F4} : 第一个参数,右对齐留10位,格式为保留4位的浮点型

  17. 常见缩写:

    • CLR: 公共语言运行库(Common Language Runtime)
    • CIL: 通用中间语言(Common Intermediate Language)
    • CTS: 通用类型系统(Common Type System)
    • CLS:公共语言规范 (Common Language Specification)
    • FCL: 框架类库(Framework Class Library)
    • BCL: 基础类库(Base Class Library)
  18. =>
    (不可重载)

    两种用法:
    1、作为lambda运算符
    2、表达式主体定义

    1:将左侧的输入参数与右侧的lambda主体分开

       Func<string> greet = () => "Hello, World!";
       int[] numbers = { 4, 7, 10 };
       int product = numbers.Aggregate(1, (interim, next) => interim * next);
    

    2: member => expression;
    expression 可以是:方法、只读属性、属性、构造函数、索引器、终结器。
    查询MSDN文档

        //方法
        public override string ToString() => $"{fname} {lname}".Trim();
        //=
        public override string ToString()
        {
           return $"{fname} {lname}".Trim();
        }
    
        //只读属性
        private string locationName;
        public string Name => locationName;
    
        //属性
        private string locationName;
        public string Name
        {
           get => locationName;
           set => locationName = value;
        }
    
        //构造函数
        public class Location
        {
           public Location(string name) => Name = name;
        }
    
        //终结器
        public class Destoryer
        {
           ~Destroyer() => Console.WriteLine($"The {GetType().Name} finalizer is executing.");
        }
    
        //索引器
        private string[] types = { "Baseball", "Basketball", "Football",
                             "Hockey", "Soccer", "Tennis","Volleyball" };
        public string this[int i]
        {
           get => types[i];
           set => types[i] = value;
        }
    
  19. using 关键字

    • using语句 定义一个范围,范围末尾将释放对象;
    • using指令 为命名空间创建别名,或导入在其他命名空间类型
      详见MSDN

二、数组

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 字段、属性

3.0.1 字段

类似C++中的成员属性,通常设置为private,并进行封装。
占用内存,存储数据

private string name;

3.0.2 属性

本身不存储任何数据,作为私有字段的访问手段;
将读写访问权限分开;
限制字段赋值范围;
不在本类中使用时,避免使用字段的名字;

//(1)原始写法--get、set访问器
public string Name
{
   get{return name;}
   set{name = value;}  //set作用域里,value总是表示调用者设置的值,并且总是和属性本身的数据类型相同。
}




//(2)另一种写法
public string Name
{
   get => name;
   set => name = value;
}     //这种写法会不断的运行函数内部语句,调用一次执行一次.
//如果里面含有 new 操作符则会引起内存资源浪费或者数据错误,因为new一次就换了一次.
//解决办法见下方(3)

//只读写法
public string Name
{
   get => name;
}
//equal
public string Name => name;
//有时候,我们并不希望由实例对象在外部轻松的修改内部字段的值,如t.Name="SharpL"
public string Name
{
   get => name;
   private set => name = value;
}




//(3)简略写法(自动属性.NET3.0及后续版本)
public string Name { get; set;} = 初值

3.0.3 索引器

类似于属性。

public int this[string key]
{
    get { return storage.Find(key); }
    set { storage.SetAt(key, value); }
}

3.1 static 静态 (上升至类级别)

  1. static 关键字把类成员定义为静态的(静态变量、静态函数),意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
  2. static 修饰的成员必须通过类来访问,而不是实例。

3.1.1 静态类

用于存放不受实例数据影响的数据和函数

  • 所有成员必须是static
  • 可以有一个静态构造函数,但不能有实例构造函数,因为不能创建实例
  • 隐式密封的,不能作为基类(父类)

3.1.2 静态构造函数

通常用来初始化类的 static 字段

class Class1
{
   static Class1()
   {

   }
}
  • 使用static关键字
  • 每个类中只能有一个,而且不能带参数
  • 不能有访问修饰符

3.1.3 静态函数

  • 在对象被创建之前就已经存在,甚至不用实例化就可以使用
  • 只能访问静态变量

3.2 类的定义及实例化

class Student
{
   public int ID{get;set;}
   public string Name{get;set;}
}
static void Main(string[] args)
{
   Student stu = new Student(){ID = 1,Name="123"}; //初始化列表
}
  1. 自定义构造器(构造函数)"ctor":
  2. 类成员"prop":

3.3 类的访问级别

  1. internal:仅限 “项目” 内的访问。<默认>
  2. public:开放。<接口的默认>
  3. abstract:仅限 “项目” 内的访问,该类不能被实例化只能被继承
  4. public abstract:开放,该类不能被实例化,只能被继承。
  5. sealed:仅限 “项目” 内的访问,该类不可被继承
  6. public sealed:开放,该类不可被继承。

3.4 继承

  1. 只能有1个基类,但可以实现多个基接口。(只能有一个亲爹,可能有多个义父)

  2. 加上 sealed限定符的表示 “封闭类” ,不可被继承。

  3. 基类与派生类:

    class 基类
    {
       ...
    }
    class 派生类 : 基类
    {
       ...
    }
    
  4. this & base 访问关键字:
    this:指代引用类的当前实例。
    base:从派生类中访问基类的成员:
    详见此博客

  5. 子类的构造:

    访问修饰符 派生类(参数列表) : base(参数列表)
    {
    	...
    } //如果父类已经对成员进行赋值了,此构造器内就不需要再赋值一次。
    
  6. 用子类来实例化父类:(多态基础)

    父类 实例名 = new 子类();
    

3.5 重写与多态(虚方法)

  1. 多态:子类需要覆写父类中的方法来实现子类特有的行为。

  2. 虚方法: 使用 virtualoverride 关键字实现方法重写:

    class Animal
    {
    	public virtual void Run()       //父类实例调用Run时,使用父类(跟实例走)
    	{
    	}
    }
    class Cat:Animal
    {
    	public override void Run()     //子类实例调用Run时,使用子类
    	{ 
    	}
    }
    
    
  3. 隐藏基类成员:
    相当于子类将父类方法继承下来,但是被本身的方法隐藏。

    class Animal
    {
    	public void Run()      //由对象类型决定,
    	{
    	}
    }
    class Cat:Animal
    {
    	public new void Run()    // new也可以不写,但是会警报
    	{ 
    	}
    }	
    
    
  4. 虚方法中必须有实现部分

3.6 抽象类与接口

为了解决多态时基类函数方法模糊、无用的问题,可以将该方法定义为抽象方法,此方法所在的类也必须是抽象类,抽象类不能被实例化,可以有普通方法、虚方法、抽象方法,但是抽象方法只能出现在抽象类中

  1. 抽象方法:与上述虚方法类似,此时用的是 abstractoverride 搭配使用,不提供实现部分,由派生类强制重写,
  2. 接口:如果是“纯虚类”(只写规范),则可以将抽象类再抽象为接口,降低耦合度。适用于多继承
    interface 接口名
    {
    	int 属性名;
    	void 方法名();
    }
    
    类似于“契约”,常用于都遵守(派生自)这个“契约”的不同类型数据作为参数时,直接使用接口作为数据类型。为了解耦合而生
     public int Sum(int [] nums)
     public int Sum(ArrayList nums)
       //合并于==>
     public int Sum(IEnumerable nums)
    

根据MS的命名规则,抽象类名应该是n.,接口名应该是adj. & adv.
e.g. 动物类 & 有嘴的
“DO name interfaces with adjective phrases, or occasionally with nouns or noun phrases.
Nouns and noun phrases should be used rarely and they might indicate that the type should be an abstract class, and not an interface.”

3.7 依赖反转

3.7.1 依赖

“汽车与汽车零部件关系”

类中包含其他类,这个类就“依赖于”其他类。(也包括再类中 new 了其他类的实例)

class Engine{ //汽车引擎类
   public int RPM{get;private set;}
   ...
}
class Car{  //汽车引擎类
   private Engine _engine; //引擎类作为成员
   public Car(Engine engine)
   {
      _engine = engine;
   }
   ...
}

3.7.2 依赖反转(DIP-Dependency Inversion Principle)

通过 “插入” 类来使原来的 “依赖关系” 变成 “实现关系”,实现类图方向的反转

依赖反转
==>由原先driver依赖于car 转变为 driver依赖于vehicle、car作为vehicle的实现。

3.8 嵌套类(内部类、成员类)

“类中类”

public class Container	//外部类
{
    public class Nested	  //嵌套类
    {
        Nested() { }
    }
}

3.8.1 内类访问外类

内部类可以访问外部类的所有成员(包括 private 和 protected)
但外部类如果想访问内部类则有限制

public class Container
{
    public class Nested
    {
        private Container parent;	//外部类类型

        public Nested()			   //D1构造函数
        {
        }

        public Nested(Container parent)	//D2构造函数
        {
            this.parent = parent;
        }
    }
}

3.8.2 内部类的实例化

	Container.Nested nest = new Container.Nested();

3.9 扩展方法

如果要增加类中的功能 :
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();

四、委托

也是一种类级别。(派生于System.MulticastDelegate类,其又派生自System.Delegate类)
类似于C++的回调函数(该函数名会被当做参数传给其他函数,从而被别的函数调用)

4.1 Func & Action 委托

  1. Action委托 只能指向返回值为空的函数。

    Action 委托名 = new Action(对象.方法名);
    委托名.Invoke(); //间接调用类的方法。
    委托名();		//效果同上。(函数指针式)
    
  2. 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”
如果多播委托返回值不是void,那么caller从最后一个被调用的方法来接收返回值;

4.2.4 委托方法添加 & 移除

类似Queue,先进先出。委托不可变,+=、-=实际上是创建了新委托。

	委托名 += 对象.方法名; //添加
	委托名 -= 对象.方法名; //移除(从队尾搜索)

如果调用列表为空,则委托为null

4.2.5 委托调用

	XX = 委托名.Invoke(参数);	//间接调用类的方法。
	XX = 委托名(参数);			//效果同上。(函数指针式)

如果委托有返回值:

  • 调用列表中最后一个方法返回的值就是委托调用返回的值。
  • 调用列表中所有其他方法的返回值都会被忽略

4.3 委托的用途

不关心函数名你,仅仅关心返回值与参数列表,简化重复代码。

假设有一个游戏结束时数据展示代码:

public class DisplayPlayerNames{
   //游戏结束结算
   void OnGameOver(PlayerStatus[] allPlayers){
      string playerNameMostKills = GetNameByMostKill(allPlayers);
      string playerNameMostFlagCaptures = GetNameByFlagCaptures(allPlayers);
   }
   //获取击杀数最高玩家
   int GetNameByMostKill(PlayerStatus[] allPlayers){
      string name = "";
      int bestScore = 0;
      foreach(PlayerStatus stats in allPlayers){
         int score = stats.kills;
         if(score > bestScore){  //冒泡
            bestScore = score; 
            name = stats.name;
         }
      }
   }
   //获取夺旗数最多玩家
   int GetNameByFlagCaptures(PlayerStatus[] allPlayers){
      string name = "";
      int bestScore = 0;
      foreach(PlayerStatus stats in allPlayers){
         int score = stats.flagCaptures;
         if(score > bestScore){  //冒泡
            bestScore = score;
            name = stats.name;
         }
      }
      return name;
   }
}

可以看到这两个函数结构极其相似,只有算分的时候不一样,这时使用delegate简化代码:

//delegate:

public class DisplayPlayerNames{
   //委托定义
   delegate int ScoreDelegate(PlayerStatus stats)
   //游戏结束结算
   void OnGameOver(PlayerStatus[] allPlayers){
      string playerNameMostKills = GetNameTopScore(allPlayers,ScoreByMostKill);
      string playerNameMostFlagCaptures = GetNameTopScore(allPlayers,ScoreByFlagCaptures);
   }
   int ScoreByMostKill(PlayerStatus stats){
      return stats.kills;
   }
   int ScoreByFlagCaptures(PlayerStatus stats){
      return stats.flagCaptures;
   }
   //获取分数最高玩家封装
   int GetNameTopScore(PlayerStatus[] allPlayers,ScoreDelegate scoreDelegate){
      string name = "";
      int bestScore = 0;
      foreach(PlayerStatus stats in allPlayers){
         int score = scoreDelegate(stats);
         if(score > bestScore){  //冒泡
            bestScore = score;
            name = stats.name;
         }
      }
      return name;
   }
}

因为函数名可以用lambda表达式替代,即:ScoreDelegate scoreDelegate = stats => stats.kills
再使用lambda表达式进一步简化

//lambda表达式:

public class DisplayPlayerNames{
   //委托定义
   delegate int ScoreDelegate(PlayerStatus stats)
   //游戏结束结算
   void OnGameOver(PlayerStatus[] allPlayers){
      string playerNameMostKills = GetNameTopScore(allPlayers,stats => stats.kills);
      string playerNameMostFlagCaptures = GetNameTopScore(allPlayers,stats => stats.flagCaptures);
   }
   //获取分数最高玩家封装
   int GetNameTopScore(PlayerStatus[] allPlayers,ScoreDelegate scoreDelegate){
      string name = "";
      int bestScore = 0;
      foreach(PlayerStatus stats in allPlayers){
         int score = scoreDelegate(stats);
         if(score > bestScore){  //冒泡
            bestScore = score;
            name = stats.name;
         }
      }
      return name;
   }
}

4.4 匿名方法

C# 2.0引入。

	委托类名 委托名 = delegate(参数列表){
						方法体;
					};

匿名方法不会显式声明返回值,方法内代码本身行为返回一个类型 = 委托的返回类型

4.5 Lambda(也是匿名方法

C# 3.0为了简化匿名方法的语法而引入。
Lambda 可采用以下任意一种形式:

1. 表达式lambda:
(input-parameters) => expression
2. 语句lambda:
(input-parameters) => { <sequence-of-statements> }

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;		    //没有参数必须加括号

任何 Lambda 表达式都可以转换为委托类型:(因为lambda本质上还是函数)

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

表达式 lambda 还可以转换为表达式树类型:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

五、事件

5.1 含义

因为事件隶属于某个主体。
所以为一种类的成员,这种成员使类或对象具备了通知能力(手机通过响铃使手机具备了通知能力)
防止委托滥用造成程序混乱(只能 +=-=
本质上是一个委托的包装器,封装了委托。

5.2 五个组成部分

  1. 发布者(Publisher):发布某个事件的类或者结构。
  2. 订阅者(Subscriber):注册并在事件发生时得到通知的类或者结构。
  3. 事件(Event):如上述介绍,被触发才会发挥功能。
  4. 事件处理器(Event Handler):S注册到事件的方法,在P触发事件时执行。
  5. 事件注册:把事件与事件处理器关联在一起

5.3 事件作用范围与功能

  • 在类内,视为普通委托使用
  • 在类外,只能使用+=、-=操作。

5.4 事件声明

  1. 完整声明:

    private XXXEventHandler 委托对象;		//用于声明事件的委托对象,含义见下。
    public event XXXEventHandler 事件对象
    {
    	add
    	{
    		this.委托对象 += value;
    	}
    	remove
    	{
    		this.委托对象 -= value;
    	}
    }
    
  2. 简略声明:

    public event XXXEventHandler 事件对象;
    

    此时编译器会自动生成一个隐藏的委托类型字段

5.5 事件订阅

常需要声明一个委托来承接这个事件

	class XXXEventArgs:EventArgs
	{
		属性;
	}

	public delegate 返回类型 XXXEventHandler(Publisher类 p对象,XXXEventArgs e);		//专门用于事件中使用的委托	

或者直接不声明,使用自带的EventHandler(object,EventArgs),并在事件处理程序中先类型转换,在使用参数。

	类.事件 += 方法名;		//直接法

	类.事件 += new 委托名(方法名);  // 委托形式

	类.事件 += delegate{方法体};	//匿名方法

	类.事件 += () => {方法体};		//Lambda

5.6 事件触发

在触发事件之前与null进行比较,从而查看事件是否包含事件处理程序。

  1. 完整版:
    if(XXXEventHandler != null)
    	XXXEventHandler(参数表);    //事件触发
    	XXXEventHandler.Invoke(参数表); //或者
    
  2. 简略版:
    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 协变

七、反射和依赖注入

需要S3接口部分的知识

7.1 接口隔离原则

“胖”接口会导致调用它的类有一些总也用不到的方法需要去实现。

为了解决这个问题需要将接口“拆分成”小接口,将原来接口本质不同的功能隔离开。
最后原本的接口继承这些子接口即可。
“调用者”不会多要,“提供者”不会少给

7.1.1 显示接口实现

有一些接口不想让用户显示地(轻易地)知道并使用

	class WarmKiller:IGentleman,IKiller
	{
		public void Love(){}
		void IKiller.Kill(){}	//显示实现:此时必须得是 IKiller 实例才可以使用 Kill 方法。
	}

   //使用(用 IKiller 类型变量引用 WarmKiller 类型对象时)
   IKiller killer = new WarmKiller();
   killer.Kill();    //调用隐藏的 Kill 方法
   //调用 Love 方法,强转
   var wk = killer as WarmKiller;
   wk.Love();

7.2 反射(Reflection)

.NET框架特有

7.2.1 什么是反射

以不变应万变以求更松的耦合
运行中的程序查看 本身或其他程序 数据的行为

你给我个对象,我能在不用 new不用知道具体类型的情况下,创建出同类型的对象,并且正常访问这个对象的成员,进一步解除耦合。

7.2.2 为什么需要反射

有时程序需要在 运行时 根据用户需要处理一些逻辑,这些逻辑或者操作很难在编写程序时全面地写出来,即使写出来了,程序体也会十分臃肿、难维护。

  • 单元测试、依赖注入、泛型编程都是基于反射机制的

7.2.3 反射优缺点

  • 优点:
    • 提高程序灵活性、可拓展性;
    • 降低耦合性;
    • 允许程序创建和控制任何类的对象,无需提前知道类的信息;
  • 缺点:
    • 性能降低:反射是在内存中动态获得对象、类型描述,效率远慢于直接代码;
    • 内部逻辑模糊: 由于反射绕过了源代码,在处理bug的时候相比于源代码直接呈现逻辑会混乱不少;

因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。

   var t = tank.GetType();          //取类型
   MethodInfo fireMI = t.GetMethod("Fire");  //取方法
   object o = Activator.CreateInstance(t);   //生成tank类型的对象o
   fireMI.Invoke(o,null);                    //调用方法

7.3 特征(Attribute)

一种允许我们向程序的程序集添加元数据的语言结构,它是用于保存程序结构信息的特殊类型的类。

  • 放置在结构前
  • []包围

7.4 依赖注入(DI-Dependency Injection)

属于封装好了的反射的重要功能
在依赖反转 DIP 概念基础上,结合接口、反射机制形成的应用

7.4.1 什么是注入

把各种类型、接口放到 容器(Container / Service Provider) 中即为“注入”

  • 注册类型的时候可以设置:创建对象时是每次都创建一个新的对象,还是创建一个单例模式(每次要对象都给同一个实例)。
  • 后面需要创建实例的时候,向Container“要实例”即可。

7.4.2 具体用法

引入nuget包:Microsoft.Extensions.DependencyInjection

  1. 基础用法:

       //(注入)
       var sc = new ServiceCollection();   //创建容器收集者对象
       sc.AddScoped(typeof(ITank),typeof(HeavyTank));  //信息注入容器
          //括号中的含义:接口,类实现了此接口的类
          //Typeof代表:动态的拿到这个类的信息
       var sp = sc.BuildServiceProvider(); //创建容器对应的服务提供者
    
       //====================分割线===============================
       
       //(要对象)
       //只要能看见ServiceProvider的地方都可以这样使用
       ITank tank = sp.GetService<ITank>();
       tank.Fire();
       tank.Run();
    
       //此时只要将HeavyTank这一处改了,就全部修改完成,不用像以前一样每个new的地方都需要修改
    
  2. 进阶用法:

       var sc = new ServiceCollection();
       sc.AddScoped(typeof(ITank), typeof(HeavyTank)); //接口(需要实例)
       sc.AddScoped(typeof(IVehicle), typeof(Truck));
       sc.AddScoped<Driver>();                         //类(无需实例)
       var sp = sc.BuildServiceProvider();
    
       //==================分割线===================================
       
       var driver = sp.GetService<Driver>(); //向sp要了个Driver
       driver.Drive();
    

7.4.3 小结

一般情况下,主体程序都会发布包含程序开发接口(API)的程序开发包(SDK)
使用SDK中的API开发插件就会比较容易、高效。
API里不一定都是接口,也有可能是一组方法,一组类,一组接口。
关于“有依赖注入为什么还需要API的原因”
依赖注入的高自由度意味着错误率提高,例如调方法时大小写写错,就无法找到对应的方法再成功调用了。
开发插件过程中,为了避免自由度过高导致的错误,我们需要有一定的约束,就是SDK中的API

八、LINQ

Language Integerated Query --- 语言集成查询

8.1 引子

在关系型数据库中,数据被放入规范的表中,并且使用SQL语句进行访问操作;
于数据库相反,在程序中,数据往往被保存在差异较大的类对象或者结构中,没有通用的查询操作语句。

8.2 方法语法与查询语法

在写LINQ查询的时候可以使用两种形式的语法:方法语法、查询语法

8.2.1 方法语法(method syntax)

int[] numbers = {2,5,28,31,17};
==============================
//1、方法语法:
var numsMethod = numbers.Where(N => N<20);  //将numbers所有小于20的数存在numsMethod中

8.2.2 查询语法(query syntax)(荐)

类似SQL语句查询

//2、查询语法:
var numsQuery = from n in numbers
                where n<20
                select n;     //效果同上

8.2.3 混合语法

//3、混合语法:
int numsCount = (
                  from n in numbers 
                  where n<20
                  select n
                ).Count();    //将小于20的读来,统计个数
posted @ 2022-09-30 14:31  LASER_06  阅读(28)  评论(0编辑  收藏  举报