Maxon's Blog
C#入门速通

C#入门速通

2

初识

类与命名空间

  • 类:构成程序的主体
  • 命名空间以树形结构组织类和其他类型 (类似C的头文件)

类库的引用:

  • 类库引用时使用命名空间的物理基础

    使用不同技术类型的项目会默认引用不同的类库

  • DLL(Dynamic Link Library)引用 (黑盒引用,无源代码)

  • Nuget :个人理解为类似Maven的一个依赖管理工具

  • 项目引用 (白盒引用,有源代码)

依赖关系

  • 类(或对象)之间的耦合关系
  • 优秀的程序 "低耦合,高内聚."
  • UML(通用建模语言)类图

🚟类

类是对现实世界事物进行抽象所得到的结果

  • 事物包括"物质"(实体)与"运动"(逻辑)
  • 建模是一个去伪存真,由表及里的过程

类与对象的关系

  1. 对象也叫实例,是类经过"实例化"后得到的内存中的实体
    • Formally "instance" is synonymous with "object" ---对象和实例是一回事
    • 有些类是不能实例化的
  2. 依照类,我们可以创建对象,这就是"实例化"
  3. 使用new操作符创建类的实例
  4. 引用变量与实例的关系
    • 孩子与气球
    • 气球不一定有孩子牵着
    • 多个孩子可以使用各自的绳子牵着同一个气球,也可以通过同一根绳子牵着气球

类三大成员

  1. 属性(property)
    • 存储数据
  2. 方法(Method)
  3. 事件(Event)
    • 类或对象通知其他类或对象的机制**,C#特有** (Java通过其他办法实现这个机制)
    • 善用机事件机制特别重要

静态成员与实例成员

  • 静态(static)成员在语义上表示它是"类的成员"
  • 实例(非静态)成员在语义上表示他是"对象的成员"
  • 绑定(binding)指的是编译器把一个成员与类或对象关联起来 (操作符 . 成员访问)

🚈类型、变量与对象(*)

什么是类型?

  • 类型又名数据类型(Data Type)

    • 是数据在内存中存储时的型号

    • 小内存容纳大尺寸数据会丢失精度,发生错误 (高精度转低精度)

    • 大内存容纳小尺寸数据会导致浪费

    • 编程语言的数据类型与数学中的数据类型不完全相同

  • 强类型语言与弱类型语言的比较

    • C语言:if条件
    • JS:动态类型
    • C#语言对弱类型/动态类型的模仿

类型在C#语言中的作用

  • 存储此类型变量所需内存空间的大小
  • 此类型的值可能表示的最大最小值范围
  • 此类型所包含的成员(方法,属性,事件等) 反射
  • 此类型由何基类派生
  • 程序运行时,此类型的变量分配在内存的什么位置?
    • Stack简介 (存方法)
    • Stack overflow 栈溢出
    • Heap简介 (存对象)
    • 使用Performance Monitor查看进程的堆内存使用量
    • 关于内存泄漏
  • 此类型所允许的操作(运算)

C#语言的类型系统

  • C#类型派生谱系
    • Object
      • 引用类型
        • 接口
        • 委托
      • 值类型
        • 结构体
        • 枚举

变量、对象与内存

什么是变量?

  • 表面上看,变量的用途时存储数据

  • 实际上, 变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量

  • 变量一共有7中

    静态变量,实例变量(成员变量,字段),数组元素,值参数,引用参数,输出形参,局部变量

  • 狭义的变量指局部变量,因为其他种类的变量都有自己的约定名称

    • 简单地讲,局部变量就是方法体里声明的变量
  • 变量的声明

    有效的修饰符组合 类型 变量名 初始化器

变量---以变量名所对应的内存地址为起点,以其数据类型所要求的存储空间为长度的一块内存区域

值类型的变量---值类型没有实例,所谓的实例与变量合而为一

引用类型的变量与实例关系---引用类型变量里存储的是对象的内存地址

常量---值不可变的变量

装箱与拆箱

  • 装箱:将值类型转换为引用类型
  • 拆箱:引用类型转换为值类型

🏎️方法的定义,调用与调试

方法的由来

  • 方法的前身是C/C++的函数---方法是面向对象范畴的概念,在非面向对象语言中仍然被称为函数
  • 永远都是类(或是结构体)的成员
    • C#语言中函数不可能独立于类(或结构体)之外
    • 只有作为类(或结构体)的成员时才被称为方法(C++中可以有全局函数)
  • 是类(或结构体)最基本的成员之一 --- 最基本的成员只有两个---成员变量与成员方法
  • 为什么需要方法和函数
    • 隐藏复杂的逻辑
    • 复用 reuse

构造器

  • 构造器(constructor)是类的成员之一
  • 狭义的构造器指的是"实例构造器(instance contractor)"

如何对方法进行debug

  • 设置断点 break point --- 程序执行到断点会暂停执行
  • 观察方法调用时的call stack --- 断点附近执行顺序
  • Step-in Step-over Step-out
  • 观察局部变量的值与变化

方法的调用与栈 *

与CPP压栈方法类似

🤷‍♂️操作符

操作符优先级

操作符(Operator)是用来操作数据的,被操作符所操作的数据称为操作数 (Operand)

优先级自上而下递减

类别运算符
基本x,y f(x) a[x] x++ x-- new typeof default checked unchecked delegate sizeof ->
一元+ - ~ ! ++x --x (T)x await &x *x
乘除+ / %
加减+ -
移位>> <<
关系和类型检测< > <= >= is as
相等` !=
逻辑与&
逻辑XOR^
逻辑OR|
条件AND&&
条件OR||
null 合并??
条件 三目?:
赋值和 lambda 表达式= *= /= %= += -= <<= >>= &= ^= |= =>
  • 可以通过圆括号提升内部表达式优先级
  • 圆括号可以嵌套

同级操作符运算顺序

  • 除了带有赋值功能的操作符 同优先级操作符都是由左向右进行运算
  • 带有赋值功能的操作符都是 从右向左运算

类型转换

as的使用

A as B 如果A是B类型则转换类型 如果不是则返回NULL

隐式(implicit)类型转换

  • 不丢失精度的类型转换
  • 子类向父类的转换
  • 装箱

显式(explicit)类型转换

  • 有可能丢失精度(甚至发生错误)的转换,即cast
  • 拆箱
  • 使用Convert类常用类型转换工具类
  • ToString方法与各数据类型的Parse/TryParse方法

自定义类型转换操作符

💻字段、属性、索引器、常量

字段

什么是字段?

  • 字段是一种表示与对象或类型关联的变量
  • 字段是类型的成员( 成员变量 )
  • 与对象关联的字段也称为 "实例字段"
  • 与类关联的字段称为"静态字段",由static修饰

字段的初始值

  • 无显式初始化时,字段获得其类型的默认值,所以字段永远都不会未被初始化.
  • 实例字段初始化的时机---对象创建时
  • 静态字段初始化的时机---类被加载(load)时

属性

什么是属性?

  • 属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态

  • 属性是字段的自然拓展

    • 从命名上看,field更偏向于对实例对象在内存中的布局,property更偏向于反映现实世界对象的特征
    • 对外:暴露数据,数据可以是存储在字段里的,也可是动态计算出来的
    • 对内:保护字段不被非法值"污染"
  • 属性由 GET/SET方法对进化而来

    C#
      public int Age
    {
           get => age;
           set => age = value;
    }
    
  • 又一个"语法糖"---属性背后的秘密

属性的声明

  • 完整声明--后台(back)成员变量与访问器(注意使用code snippet和refactor工具)
  • 简略声明--只有访问器(查看IL代码)
  • 动态计算值的属性
  • 注意实例属性和静态属性
  • 属性的名字一定是名词
  • 只读属性--只有Getter没有Setter(几乎没有人使用)

属性与字段的关系

  • 一般情况下,他们都用于表示实体的状态
  • 属性大多数情况下是字段的包装器(wrapper)
  • 建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的

索引器---使对象能够像数组相同的方式(下标)进行索引

常量

什么是常量?

  • 常量(constant)表示常量值(即,可以在编译时计算的值)的类成员
  • 常量隶属于类型而不是对象,即没有"实例常量"
    • "实例常量"的角色由只读实例字段来担当
  • 注意区分成员常量与局部常量

"只读"的应用场景

  • 为了提高程序可读性和执行效率--常量
  • 为了防止对象的值被修改--只读字段
  • 向外暴露不允许修改的数据--制度属性(静态或非静态),功能与常量有一些重叠
  • 当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)--静态只读字段

🛒参数传递

传值/输出/引用/数组/具名/可选参数,拓展方法(This参数)

传值参数

  • 值参数创建变量的副本
  • 对值参数的操作永远不会影响变量的值

传值参数 -> 值类型

传值参数 -> 引用类型,并且新创建对象

  • 创建新对象,没有对原对象进行操作,所以原对象的值并不会被改变;

传值参数 -> 引用类型,只操作对象,不创建新对象

  • 引用相当于自己指向传进来对象的地址

引用参数

引用参数 -> 值类型

  • ref修饰的参数 如ref int x;
  • 引用参数并不创建变量的副本,指向传入值的地址,修改后原值也会改变!!

引用参数 -> 引用类型,创建新对象

  • 指向对象在堆内存中的地址
  • hashcode改变 因为创建了新对象

引用参数 -> 引用类型,不创建新对象只改变对象值

  • hashcode没有改变,因为没有创建新对象
  • 此情形与传值效果一样,但是实现方式(内存机理)不一样
    • 传值方式: 两个变量都划分了内存空间,且存储了相同的地址
    • 引用方式: 只有一个变量划分了内存空间,两个变量指向同一个内存地址.

输出参数

  • out修饰,通过参数向外输出值
  • 从语义上看,out是为了输出,ref是为了引用
  • 输出形参不创建新的存储位置
  • 变量在可以作为输出形参传递之前不一定需要明确赋值
  • 在方法返回之前,该方法的每个输出形参都必须明确赋值

输出参数 -> 值类型

输出参数 -> 引用类型

数组参数

  • 必须是形参列表最后一个,由params修饰
C#
 public static void Main(string[] args)
        {
            int res = CalculateSum(1, 2, 3, 4, 5);
            Console.WriteLine(res);

        }
        static int CalculateSum(params int[] intArr)
        {
            int sum = 0;
            foreach (var item in intArr)
            {
                sum += item;
            }

            return sum;
        }

具名参数

  • 参数的位置不再受约束
  • 提高代码可读性
C#
 public static void Main(string[] args){
            PrintInfo(name: "Aowu", age: 22);//具名参数
        }
        static void PrintInfo(string name, int age){
            Console.WriteLine("Name={0} ,Age={1}",name,age);
        }

可选参数

  • 参数因为具有默认值而变得"可选"
  • 不推荐使用
C#
public static void Main(string[] args)
{
    PrintInfo();//为空

}
static void PrintInfo(string name="Wuhu", int age=21)
{
    Console.WriteLine("Name={0} ,Age={1}",name,age);
}

拓展方法(this参数)

  • 方法必须是公有的,静态的,即被public static 修饰
  • 必须是形参列表的第一个,由this修饰
  • 必须由一个静态类(一般命名为SomeTypeExtension)来统一收纳对SomeType类型的拓展方法
C#
  class Program
    {
        public static void Main(string[] args)
        {
            double x = 3.1415926;
            double y = x.Round(3); //只有一个参数
            Console.WriteLine(y);
        }
    }
    static class DoubleExtension
    {
        public static double Round( this double input/*this 修饰*/,int digits)
        {
            double result = Math.Round(input, digits);
            return result;
        }
    }

🤝委托

什么是委托?

  • 委托(delegate)函数指针的"升级版"
  • 一切皆地址
    • 变量(数据) 是以某个地址为起点的一段内存中所存储的值
    • 函数(算法) 是以某个地址为起点的一段内存中所存储的一组机器语言指令
  • 直接调用与间接调用
    • 直接调用: 通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行->返回
    • 间接调用: 通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行->返回
  • Java没有与委托相对应的功能实体(Java禁止程序员直接访问内存地址)
  • 简单使用
    • Action委托 (无返回值)
    • Func委托 (可有返回值)
C#
 class Program
    {
        public static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Action action = new Action(calculator.Report);
            calculator.Report();//直接调用
            action.Invoke();//间接调用  
            action();//同上 间接调用 

            Func<int, int, int> add = new Func<int, int, int>(calculator.Add);
            Func<int, int, int> sub = new Func<int, int, int>(calculator.Sub);
            Console.WriteLine(add.Invoke(1,2));
            Console.WriteLine(sub.Invoke(2,1));
            Console.WriteLine(add(1,2));
            Console.WriteLine(sub(2,1));
        }
        
        
    }

    class Calculator
    {
        public void Report()
        {
            Console.WriteLine("I have 3 methods!");
        }

        public int Add(int a, int b)
        {
            return a + b;
        }
        public int Sub(int a, int b)
        {
            return a - b;
        }
    }

委托的声明

  • 委托是一种类,类是数据类型,所以委托也是一种数据类型.
  • 它的什么方式与一般的类不同,主要是为了照顾可读性和C/C++传统
  • 注意声明委托的位置
  • 委托所封装的方法必须"类型兼容"
    • 返回值的数据类型一致
    • 参数列表数据类型一致
C#
public delegate double Calc(double a, double b);//public:公有 delegate:委托  double:目标方法返回值类型 Calc:名  (...):参数列表
     class Program
    {
        public static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Calc add = new Calc(calculator.Add);
            Calc sub = new Calc(calculator.Sub);
            Calc mul = new Calc(calculator.Mul);
            Calc div = new Calc(calculator.Div);
            
            Console.WriteLine(add(1,3));
            Console.WriteLine(sub(1,3));
            Console.WriteLine(mul(1,3));
            Console.WriteLine(div(1,3));
        }
    }
    class Calculator
    {
        public double Add(double a, double b)
        {
            return a + b;
        }
        public double Sub(double a, double b)
        {
            return a - b;
        }
        public double Mul(double a, double b)
        {
            return a * b;
        }
        public double Div(double a, double b)
        {
            return a / b;
        }
    }

委托的使用

实例:把方法当作参数传递给另一个方法

  • 用法1: 模板方法,借用指定的外部方法来产生结果
    • 常位于代码中部
    • 有返回值
  • 用法2: 回调方法(callback),调用指定的外部方法
    • 常位于代码末尾
    • 无返回值

缺点:

  • 一种方法级别的耦合,现实工作中要慎之又慎
  • 使可读性下降,debug难度增加
  • 把委托回调,异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
  • 委托使用不当可能会造成内存泄漏和程序性能下降

委托高级使用

多播委托 : 一个委托内封装着不止一个方法

C#
            Action action1 = new Action(student1.DoHomework);
            Action action2 = new Action(student2.DoHomework);
            Action action3 = new Action(student3.DoHomework);
           
            action1 += action3;
            action1 += action2;
            action1.Invoke(); //多播 依次执行 1 ,2 ,3

隐式异步调用:

同步与异步 :

  • 同步:你做完了我在你基础上接着做
  • 异步:你我同时做

同步调用与异步调用对比

  • 每一个运行的程序是一个进程(process)
  • 每个进程可以有一个或多个线程(thread)
  • 同步调用是在同一线程内
  • 异步调用的底层机理是多线程
  • 串行 同步 单线程,并行 异步 多线程

隐式多线程vs显式多线程

  • 直接同步调用:使用方法名

  • 间接同步调用:使用单播/多播委托的 Invoke方法

  • 隐式异步调用:使用委托的BeginInvoke

    C#
    action1.BeginInvoke(null, null);
    
  • 显式异步调用:使用Thread或Task

    C#
    Thread thread1 = new Thread(new ThreadStart(student1.DoHomework));//Thread 方式实现多线程
    thread1.Start();
    Task task1 = new Task(new Action(student1.DoHomework));//Thread 方式实现
    task1.Start();
    

应当适时使用接口(interface)代替一些委托的使用

Java完全地使用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体

C#
  class Program
    {
        public static void Main(string[] args)
        {
            IProductFactory pizzaFactory = new PizzaFactory();
            IProductFactory toycarFactory = new ToycarFactory();
            WrapFactory wrapFactory = new WrapFactory();
            Box box1 = wrapFactory.WrapProduct(pizzaFactory);
            Box box2 = wrapFactory.WrapProduct(toycarFactory);
            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

     interface IProductFactory
     {
         Product Make();
     }

     class PizzaFactory:IProductFactory
     {
         public Product Make()
         {
             Product product = new Product();
             product.Name = "Pizza";
             return product;
         }
     }

     class ToycarFactory:IProductFactory
     {
         public Product Make()
         {
             Product product = new Product();
             product.Name = "ToyCar";
             return product;
         }
     }
     class WrapFactory
     {
         public Box WrapProduct(IProductFactory productFactory)
         {
             Box box = new Box();
             Product product = productFactory.Make();
             box.Product = product;
             return box;
         }
     }

🧑‍💻事件

初步了解事件

对象能够通过事件通知其他对象

  • 事件的订阅者=事件消息的接收者=-事件的响应者=事件的处理者=被事件通知的对象

  • 事件消息=事件信息=事件数据=事件参数****

定义: Event, -- 能够发生什么事

角色: 使对象或类具备通知能力的成员

使用:用于对象或类间动作协调与信息传递(消息推送)

原理: 事件模型(event model) 中两个 5

  • "发生->响应"中的五个部分

    • 闹钟响了你起床 ...............这里隐含"订阅"关系
  • "发生->响应"中的五个动作

    1. 我有一个事件

    2. 一个人或一群人关心我的这个事件

    3. 我的这个事件发生了

    4. 关心这件事的人会被依次通知到

    5. 被通知的人根据拿到的事件信息(又称"事件数据","事件参数","通知")对事件进行响应(处理事件)

提示:

  • 事件多用于桌面,手机等开发的客户端编程,因为这些程序经常使用户通过事件来"驱动"的
  • 各种编程语言对这个机制的实现方法不尽相同
  • Java语言里面没有事件这种成员,也没有委托这种数据类型,Java的事件使通过使用接口来实现的
  • MVC.MVP,MVVM等模式,是事件模式更高级,更有效的玩法
  • 日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少

事件的应用

事件不会主动发生,只能由事件拥有者某些内部逻辑触发之后才能发生!!!

事件模型的五个组成部分

  1. 事件的拥有者(event source,对象)
  2. 事件成员(event ,成员)
  3. 事件的响应者(event subscriber, 对象)
  4. 事件处理器(event handler,成员)--本质上是一个回调方法
  5. 事件订阅---把事件处理器与事件相关联在一起,本质上是一种以委托类型为基础的"约定"

注意:

  • 事件处理器是方法成员
  • 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个"语法糖"
  • 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
  • 事件可以同步调用也可以异步调用
C#
//情况1  
public static void Main(string[] args)
        {
            Timer timer = new Timer();
            timer.Interval = 1000;
            Boy boy = new Boy();
            timer.Elapsed += boy.Action; //事件 += 事件响应者的事件处理器
            timer.Start();
            Console.ReadLine();
        }

事件的声明

完整声明

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EventFinal
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;
            customer.Action();
            customer.PayBill();

        }
    }
    //确保访问级别一致
    public class OrderEventArgs: EventArgs//作为EventArgs(事件信息)时继承EventArgs类
    {
        public string DishName { get; set; }
        public string Size { get; set; }
    }
    public delegate void OrderEventHandler(Customer customer,OrderEventArgs e);//声明委托

   
    public class Customer
    {
        private OrderEventHandler orderEventHandler;
        public event OrderEventHandler Order//完整声明
        {
            add
            {
                this.orderEventHandler += value;
            }

            remove
            {
                this.orderEventHandler -= value;
            }
        }
        public double Bill { get; set; }
        public void PayBill()
        {
            Console.WriteLine("I will pay {0}!",this.Bill);
        }

        public void WalkIn()
        {
            Console.WriteLine("Walk into thee restaurant.");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think ...");
                Thread.Sleep(300);
            }
            if (this.orderEventHandler != null)
            {
                OrderEventArgs e=new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.orderEventHandler.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.ReadLine();
            this.WalkIn();
            this.SitDown();
            this.Think();
        }
    }

    public class Waiter
    {
        public void Action(Customer customer, OrderEventArgs e)
        {
            Console.WriteLine("I will server you the dish - {0}",e.DishName);
            double price = 10;
            switch (e.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price *= 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill+=price;
        }
    }
}

简略声明(字段式声明 field-like)

C#
 public class Customer
    {
        
        public event OrderEventHandler Order;//字段式声明

        public double Bill { get; set; }
        public void PayBill()
        {
            Console.WriteLine("I will pay {0}!",this.Bill);
        }

        public void WalkIn()
        {
            Console.WriteLine("Walk into thee restaurant.");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think ...");
                Thread.Sleep(300);
            }
            if (this.Order != null)
            {
                OrderEventArgs e=new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.ReadLine();
            this.WalkIn();
            this.SitDown();
            this.Think();
        }
    }

最终版本 --使用EventHandler类而不用自己声明

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EventFinal
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;
            customer.Action();
            customer.PayBill();

        }
    }
    //确保访问级别一致
    public class OrderEventArgs: EventArgs//作为EventArgs(事件信息)时继承EventArgs类
    {
        public string DishName { get; set; }
        public string Size { get; set; }
    }
    public delegate void OrderEventHandler(Customer customer,OrderEventArgs e);//声明委托

   
    public class Customer
    {
        
        public event EventHandler Order;//使用EventHandler类而不用自己声明

        public double Bill { get; set; }
        public void PayBill()
        {
            Console.WriteLine("I will pay {0}!",this.Bill);
        }

        public void WalkIn()
        {
            Console.WriteLine("Walk into thee restaurant.");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think ...");
                Thread.Sleep(300);
            }
            if (this.Order != null)
            {
                OrderEventArgs e=new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.ReadLine();
            this.WalkIn();
            this.SitDown();
            this.Think();
        }
    }

    public class Waiter
    {
        public void Action(object sender, EventArgs e)//参数列表与EventHandler中一致
        {
            Customer customer1=sender as Customer;//类型转换
            OrderEventArgs orderInfo=e as OrderEventArgs;

            Console.WriteLine("I will server you the dish - {0}", orderInfo.DishName);
            double price = 10;
            switch (orderInfo.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price *= 1.5;
                    break;
                default:
                    break;
            }
            customer1.Bill+=price;
        }
    }
}

有了委托字段/属性,为什么还需要事件?

  • 为了程序的逻辑更加有道理,更加安全!!

所以事件的本质是委托字段的一个包装器

  • 者包装器对委托字段的访问起限制作用,相当于一个 "蒙版"
  • 封装 的 一个重要功能就是隐藏
  • 事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
  • 添加/一处事件处理器的时候可以直接使用方法名,这是委托实例所不具备的功能

用于声明事件的委托类型的命名约定

  • 用于声明A事件的委托,一般命名为AEventHandler(除非是一个非常通用的事件约束)
  • AEventHandler委托的参数一般有两个
    • 第一个是 object 类型,名字为sender,实际上就是事件的拥有者,事件的 source
    • 第二个是 EventArgs类的派生类,命名一般为AEventArgs,参数名为e,也就是事件参数
    • 一般将委托的参数列表看作是事件发生后发送给事件响应者的"事件消息"
  • 触发A事件的方法一般命名为OnA,即事出有因

事件与委托关系

事件真的是"以特殊方式声明的委托字段/实例"吗?

  • 不是! 只是声明的时候 " 看起来像 "(对比委托字段与事件的简化声明)
  • 事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托字段(实例),而event关键字则更像是一个修饰符---这就是错觉的来源之一
  • 订阅事件的时候 += 操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段 -- 错觉的又一来源
  • 事件本质是加装在委托字段上的一个"蒙版",是个起掩护左右的包装器.这个用于阻挡非法操作的蒙版绝对不是委托字段本身

为什么要使用委托类型来声明事件?

  • 站在source的角度来看,是为了表明source能对外传递哪些消息
  • 站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件
  • 委托类型的实例将用于存储(引用)事件处理器

对比事件与属性

  • 属性不是字段--很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
  • 事件不是委托字段--他是委托字段的包装器,这个包装器用来保护委托字段不被滥用
  • 包装器永远都不可能是被包装的东西!!

🚀类的继承和多态 *

类的继承

  • 类在功能上的扩展 extend
  • 只能有一个基类,但可以实现多个 (单继承多实现)
  • 类访问级别对继承的影响
  • sealed类不能被继承

成员的继承与访问

**本质:**派生类在基类原有成员基础上对基类进行横向或纵向拓展!

  • 横向:成员数量增加
  • 纵向:原有成员进行改造(版本扩展--重写)

子类访问级别不能超越父类!!

类成员的访问级别是以类成员的访问级别为上限的!!

  • 派生类对继承成员的访问
  • 派生类对基类成员的访问
  • 构造器的不可继承性 -- 父类构造器不被子类继承

面向对象的实现风格

  • Class-based
  • Prototype-based

重写

  • 重写父类方法
  • 基类可重写方法需要加上 virtual关键字
  • 子类重写父类方法需要加上override
C#
 internal class Vehicle
    {
        public virtual void Run()
        {
            Console.WriteLine("Running");
        }
    }

    class  Car : Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("Car is Running");
        }
    }

当用父类变量引用子类实例时,调用到的版本永远是继承链最新版本的子类实例 , 如果没有重写则调用继承链中最新版本的方法!!(可解释如下)

  • 由于Run方法被重写 所以输出为 Car is Running
  • 若没有 overridevirtual关键字则没有重写 只是隐藏了父类Run方法 则如下方法输出原父类方法 Running
  • 与Java不一样 Java 默认为天然重写 加了 overridevirtual关键字的情况!!!!!!!
C#
Vehicle v = new Car(); //is a 
v.Run(); //Car is Running  由于Run方法被重写 所以输出为 Car is Running  

🪢接口

抽象类与开闭原则(开放/关闭原则)

开闭原则:

我们应该封装那些不变的,稳定的,固定的和确定的成员,而那些不确定的不稳定的成员应该交给子类去实现

什么是接口和抽象类?

  • 接口和抽象类都是"软件工程"的产物
  • 具体类->抽象类->接口:越来越抽象,内部实现的东西越来越少
  • 抽象类时未完全实现逻辑的类(可以有字段和非public成员,他们代表了"具体逻辑")
  • 抽象类为复用而生:专门作为基类来使用,也具有解耦功能
  • 封装确定的,开放不确定的,推迟到合适的子类中去实现
  • 接口是完全为实现逻辑的"类" 完全抽象类 ("纯虚类";只有函数成员;成员全部public)
  • 接口为解耦而生:"高内聚,低耦合",方便单元测试
  • 接口是一个:协约,早已为工业生产所熟知(有分工必有协作,有写作必有协约)
  • 他们都不能实例化,只能用来声明变量,引用具体类(concrete class)的实例

**抽象类:**函数成员没有完全实现;

  • 与虚方法区别:虚方法非必须重写,抽象类未被实现的方法子类必须重写

**接口:**函数成员全都没有被实现;(完全抽象类) *现在好像接口内可以实现方法 个人理解是开放了多继承

  • 接口默认所有成员都是public
C#
	interface IVehicle {//接口	
        void Stop();
        void Run();
        void Fill();
    }

    abstract class Vehicle:IVehicle{//抽象类
        public void Stop(){
        Console.WriteLine("Stopped!");
        }
        public void Fill() {
            Console.WriteLine("Refule!!");
        }
        public abstract void Run();
        
    }

接口与单元测试

  • 接口的产生:自底向上(重构),自顶向下(设计)
  • C#中接口的实现(隐式,显式,多接口)
  • 语言对面向对象设计的内建支持:依赖反转,接口隔离,开/闭原则....

例子:由于Arraylist实现了IEnumerable接口,可以用IEnumerable接收可枚举的参数

C#
class Program {
        public static void Main(string[] args) {
            int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
            ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };
            Console.WriteLine(Sum(nums1));
            Console.WriteLine(Avg(nums1));
            Console.WriteLine(Sum(nums2));
            Console.WriteLine(Avg(nums2));   
        }
        static int Sum(IEnumerable nums) {
            int sum = 0;
            foreach (var num in nums) { sum += (int)num; }
            return sum;
        }
        static double Avg(IEnumerable nums) {
            int sum = 0;
            double count = 0;
            foreach (var item in nums) {
                sum += (int)item;
                count++;
            }

            return sum / count;
        }
    }

xUnit单元测试

  • 新建xUnit项目
  • 编写单元测试示例
  • 运行单元测试

注意 : 单元测试与原项目.net版本和C#版本应该保持一致!!!!

C#
namespace InterfaceLearn.Tests;

public class DeskFanTests {
    [Fact]
    public void LowerThanZero() {
        DeskFan deskFan = new DeskFan(new BadPowerSupplyLowerThanZero());
        Assert.Equal(expected:"Wont work.",actual:deskFan.Work());
    }
    [Fact]
    public void HigherThan200() {
        DeskFan deskFan = new DeskFan(new BadPowerSupplyHigherThan200());
        Assert.Equal(expected:"Warning.",actual:deskFan.Work());
    } 
}

class BadPowerSupplyLowerThanZero : IPowerSupply {
    public int GetPower() {
        return 0;
    }
}
class BadPowerSupplyHigherThan200 : IPowerSupply {
    public int GetPower() {
        return 300;
    }
}

使用moq.framework配合xUnit进行单元测试

C#
using Moq;
namespace InterfaceLearn.Tests;

public class DeskFanTests {
    [Fact]
    public void LowerThanZero() {
        var mock = new Mock<IPowerSupply>();
        mock.Setup(ps => ps.GetPower()).Returns(()=>0);//设置方法返回值
        DeskFan deskFan = new DeskFan(mock.Object); //传递实例
        Assert.Equal(expected:"Wont work.",actual:deskFan.Work());
    }
    [Fact]
    public void HigherThan200() {
        var mock = new Mock<IPowerSupply>();
        mock.Setup(ps => ps.GetPower()).Returns(()=>300);
        DeskFan deskFan = new DeskFan(mock.Object);
        Assert.Equal(expected:"Warning.",actual:deskFan.Work());
    }
}

接口隔离原则

调用者不也能多要,实现者不能多给.

  • 控制接口和类的大小
  • 多基接口实现 实现父类方法
C#
using System;

namespace ISPLearn {
    internal class Program {
        public static void Main(string[] args) {
            var driver = new Driver(new HeavyTank());//父类引用子类实例
            driver.Drive();
        }
    }

    class Driver {
        private IVehicle _vehicle;//违反接口隔离原则  应该设法分裂成俩个接口

        public Driver(IVehicle vehicle) {
            _vehicle = vehicle;
        }

        public void Drive() {
            _vehicle.Run();
        }
    }

    interface IVehicle {
        void Run();
    }

    class  Car:IVehicle {
        public void Run() {
            Console.WriteLine("Car is running...");
        }
    }
    
    class  Truck:IVehicle {
        public void Run() {
            Console.WriteLine("Truck is running...");
        }
    }


    interface IWeapon {//接口分离  武器开火 载具行驶
        void Fire();
    }
    
    interface ITank:IVehicle,IWeapon {
        void Fire();
        void Run();
    }//多基接口实现  此时实现父类IVehicle.Run 

    class LightTank:ITank {
        public void Fire() {
            Console.WriteLine("Boom!");
        }

        public void Run() {
            Console.WriteLine("Ka ka ka ...");
        }
    }
    
    class MediumTank:ITank {
        public void Fire() {
            Console.WriteLine("Boom!!");
        }

        public void Run() {
            Console.WriteLine("Ka! ka! ka! ...");
        }
    }
    
    class HeavyTank:ITank {
        public void Fire() {
            Console.WriteLine("Boom!!!");
        }

        public void Run() {
            Console.WriteLine("Ka!! ka!! ka!! ...");
        }
    }
    
    
}

显式接口隔离 C#独有

  • 只有实例为 IKiller类型时才能调用kill方法
C#
using System;

namespace ISPLearn {
    public class ISP3 {
        public static void Main(string[] args) {
            //显式接口隔离
           var wk = new WarmKiller();
            wk.Love();
            
            IKiller wk2 = new WarmKiller();//只可访问Kill方法  不可访问Love方法
            wk2.Kill();

            var wk3 = wk2 as WarmKiller;//转换类型
            wk3.Kill();
            wk3.Love();

        }

        interface IGentleman {
            void Love();
        }

        interface IKiller {
            void Kill();
        }

        class WarmKiller:IGentleman,IKiller {
         //Usage 1
            /*private IKiller _killerImplementation;//显示接口隔离
            public void Kill() {
                 Console.WriteLine("Let me kill enemy ...");
            }*/
		//Usage 2  
            void IKiller.Kill() {
                Console.WriteLine("Let me kill enemy ...");
            }
            
            public void Love() {
                Console.WriteLine("i love u forerver ...");
            }
        }
        
    }
}

🪞反射( Reflection)与依赖注入

(69条消息) C#的反射_牛奶咖啡13的博客-CSDN博客_c# 反射

  • 反射:以不变应万变(更松的耦合)
  • 反射与接口的结合
  • 反射与特性的结合
  • 依赖注入:此DI非彼DI,但没有DI就没有此DI..

反射指程序可以动态访问、检测和修改它本身状态或行为的一种能力。

C#
//其余部分代码见  接口隔离原则部分
public static void Main(string[] args) {
            ITank tank = new HeavyTank();
            // 依赖注入
            // ======== 分割线 =====  以下不再使用静态类型
            var t = tank.GetType();
            object o = Activator.CreateInstance(t);
            MethodInfo fireMi = t.GetMethod("Fire");
            MethodInfo runMi = t.GetMethod("Run");
            fireMi.Invoke(o, null);
            runMi.Invoke(o, null);

        }

三方插件--一般使用

App.cs

C#
using System.IO;
using System.Runtime.Loader;

namespace ReflectDILearn; 

public class ReflectDILearn {
    public static void Main(string[] args) {
        var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
        var files = Directory.GetFiles(folder);
        var animalTypes = new List<Type>();
        foreach (var f in files) {
            var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(f);
            var types = assembly.GetTypes();
            foreach (var t in types) {
                if (t.GetMethod("Voice")!=null) {
                    animalTypes.Add(t);
                }
            }
        }

        while (true) {
            for (int i = 0; i < animalTypes.Count; i++) {
                Console.WriteLine($"{i+1}.{animalTypes[i].Name}");
            }

            Console.WriteLine("===================");
            Console.WriteLine("Please Choose animal:");
            int index = int.Parse(Console.ReadLine());
            if (index>animalTypes.Count||index<1) {
                Console.WriteLine("No such an animal.Try again!");
                continue;
            }

            Console.WriteLine("Times?");
            int times = int.Parse(Console.ReadLine());
            var t = animalTypes[index - 1];
            var m = t.GetMethod("Voice");
            var o = Activator.CreateInstance(t);
            m.Invoke(o, new object?[] { times });
        }
    }
}

animals.Lib

C#
namespace Animals.Lib;

public class Cat {
    public void Voice(int times) {
        for (int i = 0; i < times; i++) {
            Console.WriteLine("Meow!");
        }
    }
}

libs写好之后Build Solution 生成的dll文件放到app.cs中Animals文件夹 读取文件夹中动物类 并进行操作.....

三方插件--SDK使用

为了让其他开发者能够更轻松的二开程序 可以给出SDK

步骤

  1. 写一个数据接口 以Animals为例

    C#
    namespace ReflectDILearn.SDK; 
    public interface IAnimal {
        void Voice(int times);
    }
    
  2. 定义一个UnfinishedAttribute类,此类继承自 Attribute (可以什么都不用干)

    C#
    namespace ReflectDILearn.SDK; 
    public class UnfinishedAttribute:Attribute {
    }
    
  3. Build Solution

  4. 找到dll文件

  5. 第三方引用SDK.DLL

  6. 继承SDK接口

    C#
    using ReflectDILearn.SDK;
    namespace Animals.Lib2;
    
    public class Dog:IAnimal {
        public void Voice(int times) {
            for (int i = 0; i < times; i++) {
                Console.WriteLine("Woof!");
            }
        }
    }
    
  7. 若部分内容未做完可以添加 [unfinished]

    C#
    using ReflectDILearn.SDK;
    
    namespace Animals.Lib2; 
    [Unfinished]
    public class Cow:IAnimal {
        public void Voice(int times) {
            for (int i = 0; i < times; i++) {
                Console.WriteLine("Moo!");
            }
        }
    }
    
  8. 完成之后 build solution 主体程序导入Animals 的dll文件

    • 注意修改之处与前文区别 因为此时已知类型为IAnimals 所以直接强制转换获取实例
    C#
    using System.IO;
    using System.Runtime.Loader;
    using ReflectDILearn.SDK;
    
    namespace ReflectDILearn; 
    
    public class ReflectDILearn {
        public static void Main(string[] args) {
            var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
            var files = Directory.GetFiles(folder);
            var animalTypes = new List<Type>();
            foreach (var f in files) {
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(f);
                var types = assembly.GetTypes();
                foreach (var t in types) {//注意此处修改
                    if (t.GetInterfaces().Contains(typeof(IAnimal))) {
                        if (t.GetInterfaces().Contains(typeof(IAnimal))) {
                            //获得Unfinished标记
                            var isUnfinished = t.GetCustomAttributes(false).Any(a => a.GetType() == typeof(UnfinishedAttribute));
                            if (isUnfinished) continue;
                            animalTypes.Add(t);
                        }
                        
                    }
                }
            }
    
            while (true) {
                for (int i = 0; i < animalTypes.Count; i++) {
                    Console.WriteLine($"{i+1}.{animalTypes[i].Name}");
                }
    
                Console.WriteLine("===================");
                Console.WriteLine("Please Choose animal:");
                int index = int.Parse(Console.ReadLine());
                if (index>animalTypes.Count||index<1) {
                    Console.WriteLine("No such an animal.Try again!");
                    continue;
                }
    
                Console.WriteLine("Times?");
                int times = int.Parse(Console.ReadLine());
                var t = animalTypes[index - 1];
                var m = t.GetMethod("Voice");
                var o = Activator.CreateInstance(t);
                var a = o as IAnimal;//此处已修改
                a.Voice(times);
            }
        }
    }
    

依赖注入

  • nuget 安装 Microsoft.Extensions.DependencyInjection
  • 我的理解是类似Spring的容器的 AutoWire
C#
public static void Main(string[] args) {
            var sc = new ServiceCollection();//容器
            //ITank是静态类型  typeof(ITank)才是ITank动态的类型描述
            sc.AddScoped(typeof(IVehicle), typeof(Car));
            sc.AddScoped<Driver>();
            var sp = sc.BuildServiceProvider();
            // ==== 分割线==  以上为注册  以下为向ServiceProvider获取对象
            var Driver = sp.GetService<Driver>();
            Driver.Drive(); //AutoWire
        }

⚙️泛型

泛型(generic)无处不在

  • Q:为什么需要泛型? A:避免成员膨胀或者类膨胀
    当需要增加更多的类型时,在添加新的类称为类型膨胀,或者一直添加某个类中的成员称为成员膨胀

    C#
    class Apple {
        public string Color { get; set; }
        
    }
    
    class Book {
        public string Name { get; set; }
    }
    
    //.....若此处一直添加新类型  一般情况下会有如下方法
    
    class AppleBox {//添加新类  类型膨胀
        public Apple Cargo { get; set; }
    }
    
    class Box {
        public Apple Cargo { get; set; }
        public Book Book { get; set; }// 添加新成员 成员膨胀
        
    }
    
  • 正交性:

    泛型类型(类/接口/委托...) 泛型成员(属性/方法/字段 ...)

  • 类型方法的参数判断

  • 泛型与委托,lambda表达式

泛型一般使用

泛型类

C#
using System.Drawing;
namespace TemplateLearn;
class Program {
    public static void Main(string[] args) {
        Apple apple = new Apple(){Color = "Red"};
        Book book = new Book() { Name = "A Book" };

        Box<Apple> box1 = new Box<Apple>() { Cargo = apple };//使用泛型
        Box<Book> box2 = new Box<Book>() { Cargo = book };

        Console.WriteLine(box1.Cargo.Color);
        Console.WriteLine(box2.Cargo.Name);
    }
}
class Apple {
    public string Color { get; set; }   
}

class Book {
    public string Name { get; set; }
}

class Box<TCargo> { //<>中为类型参数  定义
    public TCargo Cargo { get; set; }    
}

泛型接口

C#
namespace TemplateLearn;

class Program {
    public static void Main(string[] args) {
        Student<int> student = new Student<int>();//表示id为int类型的学生id  可以设置为其他类型如 ulong....
        student.ID = 1;
        student.Name = "Aowu";
        Console.WriteLine(student.ID);
    }
}

interface IUnique<Tid> {
    Tid ID { get; set; }
}

class Student<Tid> : IUnique<Tid> {//实现泛型接口的类 也是泛型类
    public Tid ID { get; set; }
    public string Name { get; set; }
}

class Student : IUnique<ulong> { //若指定泛型接口类型 则此类不是泛型类 实现的是特化之后的泛型接口
    public ulong ID { get; set; }
}

泛型委托

Action

Action委托只能引用没有返回值的方法 <>中的类型为方法入参类型

C#
using System.Collections.Generic;

namespace TemplateLearn;
class Program {
    public static void Main(string[] args) {
        //Action委托只能引用没有返回值的方法  <>中的类型为方法入参类型列表
        Action<string> say = Say;//委托   
        say.Invoke("Aowu");
        say("Aowu!");
        Action<int> mul = Mul;
        mul.Invoke(10);

    }

    static void Say(string str) {//参数为string
        Console.WriteLine($"Hello,{str}!");
    }

    static void Mul(int x) {//参数为int
        Console.WriteLine(x*100);
    }
}

Func

Func委托可以引用带返回值的方法

C#
namespace TemplateLearn;
class Program {
    public static void Main(string[] args) {
        //类型参数为 入参类型列表, 最后一个参数为返回值类型
        Func<double, double, double> addInt = Add;
        var res = addInt.Invoke(100.2, 300.1);
        Console.WriteLine(res);
    }

    static int Add(int a, int b) {
        return a + b;
    }
    static double Add(double a, double b) {
        return a + b;
    }
}

lambda表达式

(参数列表)=>{方法体}

C#
public static void Main(string[] args) {
    // (参数列表)=>{方法体}   应该就是匿名函数吧 
    Func<double, double, double> addInt = (a,b) => { return a + b;};
    var res = addInt.Invoke(100.2, 300.1);
    Console.WriteLine(res);
}

partial类

C#编译器允许我们把一个类分成多个部分进行编译,每一块是自己的逻辑单元并可以进行自己的版本更新.

  • 所有部分类的命名空间与类名 需要保持一致
  • 甚至允许不同部分用不同编程语言编译
C#
namespace mynamespace;
public partial class ClassPartial{}//partial修饰的类为部分类
//... 部分类其余部分的命名空间需要保持一致
  • 减少类的派生
  • partial类与Entity Framework
  • partial类与Windows Forms,WPF,ASP.Net Core

⛓️枚举类型

  • 认为界定取值范围的整数
  • 整数值的对应
  • 比特位式用法
C#
using System.Collections.Generic;

namespace TemplateLearn;
class Program {
    public static void Main(string[] args) {
        Person bigBoss = new Person();
        bigBoss.Level = Level.BigBoss;
        Person employee = new Person();
        employee.Level = Level.BigBoss;
        
        Console.WriteLine(bigBoss.Level>employee.Level);//可以比较大小
        
        //======== 比特位用法 ↓ ===
        //可以同时选择多个枚举值
        bigBoss.Skill = Skill.Drive | Skill.Cook | Skill.Program | Skill.Teach;//按位取或   =相加
        Console.WriteLine(bigBoss.Skill); //15  1111 
        Console.WriteLine((bigBoss.Skill & Skill.Cook)>0);//若大于0 则表示会做饭
       	Console.WriteLine((bigBoss.Skill & Skill.Cook)==Skill.Cook);//若相等 则表示会做饭
    }
}
enum Skill {
    Drive=1, //0001        //这四个数各位1错开
    Cook=2, //0010
    Program=4,//0100
    Teach=8,//1000
}
enum Level {
    Employee=100,
    //可以手动指定对应值  如果只赋值了一个 则后面的依次+1
    //如现在Employee=100,则Manager=101  Boss=102 ...
    Manager,
    Boss,
    BigBoss,
}

class Person {
    public Level Level { get; set; }
}

📃结构体

  • 值类型,可装箱拆箱
  • 可实现接口,不能派生自类/结构体
  • 不能有显式无参构造器

值类型: 变量之间赋值的时候,变量之间Copy的是一个完整的对象

引用类型:变量之间赋值的时候,变量之间Copy的是同一个对象的引用

C#
class Program {
    public static void Main(string[] args) {
        Student stu = new Student(){ID=101,Name = "Aowu"};
        object obj = stu;//装箱 obj引用堆内存中的 Student实例
        Student student = (Student)obj;//拆箱
        Console.WriteLine($"{student.Name},{student.ID}");//Aowu,101

        Student stu2 = stu;
        Console.WriteLine($"{stu2.Name},{stu2.ID}"); //Aowu,101
        stu2.ID = 102;
        stu2.Name = "Wuhu";
        Console.WriteLine($"{stu2.Name},{stu2.ID}");//Wuhu,102
        
        stu2.Speak(); //Wuhu is speaking...
        
        Student stu3 = new Student(1, "Oyo");
        Console.WriteLine($"{stu3.Name},{stu3.ID}");//Oyo,1

    }
}
interface ISpeak {
    void Speak();
}
struct Student:ISpeak {
    public int ID { get; set; }
    public string Name { get; set; }
    public Student(int id, string name) {
        ID = id;
        Name = name;
    }
    public void Speak() {
        Console.WriteLine($"{this.Name} is speaking...");
    }
}

💼委托

什么是委托?

**委托类型:**实际上是一种类类型.(引用类型)

一般情况下类是用来反映现实世界的事物,委托类型是用来包裹着一些方法的,通过委托类型实例间接调用一些方法 (方法的包装器)

**功能:**一个函数或者一组函数的包装器

委托类型是怎么声明出来的?

C#
class Program {
    public static void Main(string[] args) {
        //使用委托类型
        //包裹Add方法
        MyDele dele1 = new MyDele(Add);
        //可以包裹多个方法
        //多播委托:一次调用多个方法
        //dele1 += Method1;
        //通过委托实例间接调用此方法
        Console.WriteLine(dele1.Invoke(1,2)); //也可使用dele1();
    }
    static int Add(int x,int y) {
        return x + y;
    }
}
delegate int MyDele(int a,int b);//声明委托类型  返回类型为:int  参数列表为: int int

泛型委托

C#
class Program {
    public static void Main(string[] args) {
        //泛型委托
        //特化为int 特化:指定泛型的类型
        MyDele<int> deleAdd = new MyDele<int>(Add);
        Console.WriteLine(deleAdd(100,210));//Console:310
        MyDele<double> deleMul = new MyDele<double>(Mul);
        Console.WriteLine(deleMul(1.3,21.0));//Console:27.3
        //委托是一种类类型
        Console.WriteLine(deleAdd.GetType().IsClass);//Console:True
    }
    static int Add(int a, int b) {
        return a + b;
    }
    static double Mul(double a, double b) {
        return a * b;
    }
}
delegate T MyDele<T>(T a, T b);

我们必须自己创建委托类型吗?

并不是,大多数情况下我们不必创建自己的委托类型,C#已经为我们准备了两个泛型委托,具体详见泛型--泛型委托

🧩lambda表达式

Lambda

  1. 匿名方法
  2. Inline方法
  3. (参数列表)=>{方法体}
C#
public static void Main(string[] args) {
    //泛型委托的类型推断,由于输入的 100,200 都是标准的int类型所以认定T类型为int
    DoSomeCalc(((a, b) => { return a * b;}),100,200);
}
static void DoSomeCalc<T>(Func<T, T, T> func,T x,T y) {
    
    Console.WriteLine(func(x, y));
}

🎈LINQ

LINQ:.NET Language Integrated Query

..未完