c#学习

配置环境

用vs code IDE方法一

  1. 在桌面新建文件夹 ,
  2. 到vs code 中选 用文件夹打开
  3. 点击 终端 新建终端
  4. 输入 dotnet new console 新建一个控制台
  5. 输入 dotnet run 就可以运行了
  6. 告诉 vs code 怎么运行代码 shift +ctrl +p >打开命令选项板command palette

command palette

  1. 显示控制台
    1.点击vs code 中的launch(开始{应用程序})
    2.修改 console:“internal Console(内部的)”,改为externalTermianl(外部,终端)

    "console": "externalTerminal",
    
  2. 最后输入代码 console.Readkey(); 控制什么时候结束窗口.

  3. 打开 cmd 控制台 输入dotnet new -l 获得dotnet可以创建的类型

    控制台应用 console [C#],F#,VB Common/Console

  1. 创建 .NET 控制台应用程序

操作符详解

操作符

什么是操作符:

  1. 操作符(operator)也叫“运算符”
  2. 操作符是用来操作数据的,被操作的数据也叫操作数。
  3. 操作符的运算中 同个级别 操作符都是从左向右--其中赋值操作符-->从右向左。
    操作符在下表格中类型排名越前优先级越高。

为什么要使用操作符?

  • 操作符的本质是一种方法(函数,即算法)的“简记法”
  • 如果没有+,只有add函数,那么3+4+5可以写成
  • Add(Add(3,4),5)
  • 操作符会受到数据类型的限制。
int a =5;
int b =4;
int c =a / b ;//因为是int类型 所以结果会被限制为int类型 结果为1

Console.WriteLine(c);
  • 如何创建一个自己的操作符方法?

  • 操作符优先级通过 () 提高优先级

一,基本操作符

  • 优先级最高 参与构成基本表达式(表达一定运算意图的式子)

1,成员访问操作符 x.y

System.IO.File.Create("D\\hello,world.txt");
//访问了system命名空间的子集IO 调用了IO中file(静态成员)
//并调用了create方法创建了一个文本
//符合1,2,3
dotnet add package System.Windows.Forms//在vscode中使用form要中终端引用。
  1. 外层名称空间中的子级名称空间
  2. 名称空间中的类型
  3. 类型中的静态成员
  4. 访问对象成员需要new一个实例

2,方法调用操作符 f(x)

f(x) f值函数(function) x 方法调用的参数

Calculator c=new Calculator ();

int x=c .Add(10,20); //方法 (参数)

在委托(间接调用)类型中 f(x)中被调用的方法不用(),

Action myAction=new Action (c.Print);//只方法名字就可以委托了
myAction ();

3,元素访问操作符 a[x]

a[x]中的[x]表示的是索引

  1. 在数组中
int []myIntArray=new int []{1,2,3};
//int 表示数组的默认类型 []表示数组的长度 {}数组初始化器。
//[]可以不写 写了要和后面初始化器里面的长度对应。
Console.WriteLine(myIntArray[0]);
//这的[]表示访问数组元素 从0开始,代表第一位 表示集合的索引。
Console.WriteLine(myIntArray.Length-1);
//表示不会超出边界
  1. 在字典中

索命元素访问操作符中[]代表的是集合索引,不一定是整数。

Dictionary<string ,Stundent >stuDic=new Dictionary<string, Stundent> ();
//<>表示泛型类 需要与其他的类型组合在一起才能形成一个完整的类
//Dictionary<索引类型,值类型>
//stuDic字典中 索引是一个string类型,值是一个类
for (int i = 1; i <= 100; i++) //创建100个学生,名字为s_+编号,分数为100+i++
{
   Stundent stu =new Stundent();
   stu .Name ="s_"+i.ToString();
   stu.Score=100+i ;
   stuDic.Add(stu.Name ,stu ); //将名字作为索引值,将student的实例作为值保存到字典中

}
//[]代表的是字典的索引
Stundent number6=stuDic["s_6"];//访问 student类中 索引值为s_6的元素
Console.WriteLine(number6.Score);//打印6的出分数。
Console.ReadKey();
class Stundent
{
   public string Name;
   public  int  Score;
}

4,后置++ 后置-- 操作符 x++ x--

遇到赋值运算符:先赋值,再运算。

int a =100;
int b =a ++ ;//这时b=100,a=101

5,typeof操作符与default操作符

typeof 操作符帮助我们查看一个类型的内部结构(Metadate源数据).

简视一个类型的内部结构(以double为例)

Type d =typeof(double);
Console.WriteLine(d.Namespace);
Console.WriteLine(d.FullName);
Console.WriteLine(d .Name );
int c=d.GetMethods().Length;//得到方法数组的长度
foreach(var fullName in d.GetMethods())//得到方法的名字
{
 Console.WriteLine(fullName.Name );
}
Console.WriteLine(c );

default(默认) 获得数据的默认值。 结构体默认值

int x=default(int );
//就是把里面所以的数据都改为0,结果为0

引用类型默认值 是0 ——是把内存块都改为0

枚举类型默认值

level level=default(level);
Console.WriteLine(level );
Console.ReadKey();
//如果赋值排序 要赋值0 ,才不会出错,默认值为0所在的值
enum level//没有赋值默认就是排在第一个的值

{
   low,//1ow=1,mid=2,hig=3 不对 要从0开始
   mid,
   high
}

6,new操作符

new 操作符在内存中创造一个实例,并调用实例构造器(instance constructor)。


from  myForm =new from ();
//右边 new from创建实例 ()调用实例构造器
//左边 new操作符拿到的内存地址 ,交给要访问的引用变量myForm。

  1. var x 声明隐式变量,但是变量类型确定后就不可以转换变量类型了,可以换值。

调用实例的初始化器,可以初始化多个。
是加上{}:from myForm =new from (){Text=“Hello,...,...”};

直接访问方法,不创建实例
new form(){}.ShowDialog();
因为没有引用变量牵住它,所以是一次性的,会被回收。无法再次调用

  1. 语法糖衣:例如一.string是引用类型,但不用new ,内置语法糖衣
    例如二,int[]myArray=new int[10];可以写成int[]myArryay={1,2,3};

  2. 匿名类型 为匿名类型创建对象并且用隐式类型变量来引用这个实例。

var cat=new {Name="汤姆",Age=8};
//new 实例后面跟一个对象初始化器,不知道具体是什么数据类型,就用var引用这个实例
Console .WriteLine(cat.Name);
Console.WriteLine(cat .GetType().Name);
  1. new操作符的注意事项 一,耦合性高,依赖性高

依赖注入设计模式:可以将紧耦合 改为相对松的耦合方式。

  1. new 关键字 在类的继承中 如果子类使用 new public void a(){}作为修饰符 可以隐藏父类的方法。少见

7,Checkded(检查) Unchecked操作符

checked检查一个值是否在内存中有溢出。 使用方法一 用做操作符

uint x =uint .MaxValue;
uint y=checked(x+1);//结果是二进制每一位加1 结果为0
//c#默认是unchecked;

使用方法二 用做语句块

checked
{
uint x =uint .MaxValue;
uint y=x+1;
}

8,delegate()操作符

delegate(委派 ... 为代表)也是关键字,操作符使用淘汰了,通常用来作委托用。 现在用lambda表达式使用。

9,sizeof 操作符

sizeof 对象在内存中所占字节数。

注意事项:1 只能获得基本数据类型(有关键字的,是结构体类型的)所以排除string,object。
2 在非默认的情况下 可以获得自定义结构体的实例 要放在不安全的上下中运行。 需要配置不安全环境 unsafe

10,-> 操作符

类似与c,c++指针,用来取地址,只能用来操作结构体类型,还要在不安全的环境下运行。

二,一元操作符

一元操作符也叫单目操作符,只有一个操作数。

1,取地址操作符 &x,取引用操作符 *x

不常用,也要不安全的情况下使用。

2,+ - ! ~操作符

正+,负-,非!,反~

+,-与数学不一致的地方:数据类型是值是会溢出的,最小的绝对值和最大的绝对值是不对称的。 ~,取反操作符:在一个数的二进制是按位取反,与相反数不同,相反数是按位取反还要加1

!取非操作符:只能用来操作布尔类型的值。真变假,假变真。

3,++x --x操作符

先运算++,--,后进行赋值运算。

4,T(x)操作符

T(X)强制类型转换操作符。

类型转换的方式:

  1. 隐式(implicit)转换类型 //编译器自动转换。 不丢精度(字节小的转字节大的):

    父类可以隐式转换子类 ,但是不能调用子类的方法
    在C#中引用变量只能访问 变量类型的方法,成员,不能访问变量引用实例类型的方法

    Teacher t=new Teacher();
    Human h=t;//可以完成隐式转换,但不能调用子类方法。
    class Animal{}
    class Human:Animal{}//指定基类(父类),子类会继承基类里面的成员。
    class Teacher:Human{}
    

    装箱(把一个值类型从栈搬到堆里面)

  2. 显示(explicit)转换类型//需要手动指定。

    • 有可能丢失精度(甚至错误),T(X),也叫cast(铸造)把要转换的类型看成是铁水,转换的结果是一个模子,模子太小会装不下铁水,铁水就会丢失(数据也就错了); 方法:T(X)转换的结果(要转换的变量):uint x=100; ushort y=(ushort)x;

    • 拆箱

    • 类型差距太大不能用cast

1,convert(变换) convert.To转换的结果(要转换的变量)//是在system名字空间下的方法

string srt=Convert.ToString(q );

2,数值类型转文本程序:

convert.ToString(要转换的变量)=(要转换的变量)ToString

ToString是object(基础父类)带有的默认方法,所以类型都有

3,parse解析,是在变量里面的方法,而且一定要是可以转换的
转换的结果.Parse(要转换的变量)

string str="10";
uint ui=uint.Parse(str);

这样很不方便,容易出错,就有了TryParse,结果是布尔值来判断是否正确。

  • 类型转换也是一种与语法糖衣 显示类型转换符就是一个目标类型的实例的构造器,但是这个构造器写在被装换的类型里面
Stone stone=new Stone ();//声明一个叫石头的类 ,达到一定的条件就变成猴子
stone.Age =5000;
Monkey m=(Monkey)stone;
Console.WriteLine(m.Age );
class Stone
{
  public int  Age;
  public static explicit operator Monkey(Stone stone )
  {
      //条件是每过500年变有猴子1岁
      //隐式类型转换就是把explicit 改为implicit。
      Monkey m=new Monkey();
      m.Age=stone.Age/500;
      return m;
  }
}
class  Monkey
{
  public int Age;
}  

三,算数运算操作符

/ * % + -
与数学基本一致
注意事项

  1. 操作符与数据类型相关
  2. 留心数值提升 有NAN 正无穷+∞ 负无穷-∞,特殊值 在整数中 除法中 被除数不可以是0,但在浮点数中可以结果为正数为+∞,负数为-∞。
    dobule.PositiveInfinity正无穷 dobule.Negativeinfinity负无穷如果相除结果为NAN(无)。
  3. 加法还可以连接字符串。

四,位移操作符

<< >>数据在二进制结构向左,向右平移.

没有溢出的情况下 左移 就是*2 右移/2 左移无论正负补进来的都是0

右移更容易溢出,如果是正数最高位补0,负数补1.

五,关系和类型检测操作符

1,关系操作符

找两个操作数的关系,结果为布尔类型 >,>=,<,<=优先级高于== ,!=

字符串只能比较是否相等

string.ToLower(),转为小写

2,类型检测操作符 is(结果为布尔值) 检验一个对象是不是某个类型的对象
检测对象 is 检测结果
如果是某个类型的子集,检测是不是父级,结果为真。类似于人是动物

as 检测对象 像不像 检查结果,如果像,就把值交给检测对象,不像就给一个null值。 检测对象 as 检测结果

六,逻辑与 & 逻辑或| 逻辑^异或

也叫位或,位与,

& 位与,二进制按位求与 ,1为真,0为假。有假则假。

| 位或 二进制按位求或,有真则真。

^ 位异或 两位不一样真,一样假。

七,条件与 && 条件或||

操作布尔值的。

或,与都要避开短路效应:结果成立就不执行后面的代码。

int x=3;
int y=4;
int a=3;
if (x>y&&a++>3)//这里的a++没有运行。
{
    Console.WriteLine("可以");
}
Console.WriteLine(a );

八,null 合并 ??

null合并 可空类型(并不是0的意思,占时保留空值)。 Nullable<数据类型>x=null;
还是可以进行赋值的x=100;

语法糖衣为:数据类型?x=null; int ?x=null;

int y=x??代替的值;int y=x??59; //如果x的值为空,用什么值代替。

string.Empty,表示字符串的空值string str=string.Empty;

九,条件控制符 ?:也叫三元控制符

是对if else 的简写。

string str=(条件语句)?如果为真返回值:如果为假返回值;

表达式,语句

表达式

表达式(expressions)是如何语言的基本组件之一,专门用来求值的语法实体。

在c#中 表达式是算法的最基本(最小)单元,表达一定的算法意图。
要有一个||多个操作数 && 没有||多个操作符组成的序列。

  • 表达式的结果
  1. a single value int x=100; x++;//得到一个值
  2. object new form();//得到对象
  3. method(方法) 委托:管理某些方法 Action myAction-new Action(console.WriteLine);console.WriteLine就是由类名和方法构成的成员访问表达式,结果是一个方法。
  4. name space System.Windows.Forms.Form myform=new form();System.Windows就是主名称空间.子名称空间。

表达式的优先级,根据操作符的优先级决定。

  • 具体有那些表达式
    1. A value.任何得到值(结果)的运算。值都有自己的数据类型。在编程中:值(结果)的数据类型也是表达式的数据类型。
    2. A variable(变量)
    3. A namespece
    4. A type(类型)var t=typeof(int32);
    5. A method group(方法组)Console.Writeline();拿到的是一组方法根据数据类型选择调用的方法(重载决策)。
    6. A null literal(空字面量) Form myform=null;
    7. An anonymous function(匿名函数)
    8. A property access(属性访问)
    9. An event access(事件访问)
    10. An idexer access(索引器访问)
    11. Nothing 对返回值为void的方法的调用。

语句详解

语句定义:广义最小的独立元素,功能表达一些将被执行的动作。等价于一个或一组有明显逻辑关联的指令。语句由表达式组成。
语句是高级语言中独有的(高级语言中的表达式对应低级语言中的指令)

c#中定义
让程序员顺序地(sequentiall)表达算法思想。通过条件判断和循环等方法控制程序的逻辑走向。
功能:陈述算法思想,控制逻辑走向,完成有意义的动作action
c#中语句以;结尾,但以;结尾不一定是语句using System指令 |||public string Name;字段的声明
语句一定是在方法体里面的。

语句分类:labeled-statement标签语句(用的不多),declaration-statement声明语句,embedded-statement嵌入式语句

一,declaration-statement声明语句

  1. 局部变量声明 数据类型(var||具体类型)+局部变量声明器(变量名字,可以多个变量同时声明)
    int x=10,y,z=3;//这里的=号是本地变量初始器,不叫赋值。这么写会减低可读性。
    int[]myArray={1,2,3}//数组初始化器是{}。
    y=100;//这里是赋值的含义。
    var a="你好";
    
  2. 局部常量声明 常量初始化后值不能改变的量,常量一定要有初始化
    const double pi=Math .PI;
    

二,embedded-statement嵌入式语句

1,表达式语句expression-statement

这些表达式如果有值,没有用变量接受值,值会被丢弃。
遇到只是想要表达式执行的动作时候可以使用。

Add (5,2);//只是想执行加法,如想要看到结果要在方法里面打印出来。
//少用 一个方法最好就做一件事情;。
static int  Add(int a,int b)
{
    int result=a +b ;
    //在方法中就完成打印的步骤
    Console .WriteLine("result is {0}",result);
    return result ;
}
  • 调用表达式 invocation-expression Console.WriteLine();//()代表使用方法调用表达式+;构成语句

  • 对象创建表达式object-creation-expression
    用new操作符构成的表达式new form();只能用一次

  • 赋值表达式 assignment int x; x=100

  • x; --x; x; x--; 表达式

  • await-expression 异步编程用。

2,块语句 block

块语句是一个语句容器,容纳多条语句,但是算一条语句。

格式:{语句列表}//可以空。什么语句都可以写在块语句里面。
{}出现在方法体里面才是块语句。不用加;

变量的作用域:块语句不能访问块语句内变量;但块内部可以访问外部变量。

3,选择(判断,分支)语句 selection-statement
  • if语句 if(布尔类型表达式)+一条嵌入式语句//整个if是一条语句。申明语句和标签语句不能放在if嵌入语句中的

    if(布尔类型表达式)+一条嵌入式语句(如果布尔值为true会执行)else+一条嵌入式语句(如果布尔值为false执行)

 if (true)
{
   Console.WriteLine("你好");
}

if嵌套使用

//这里还要有一个try 来捕捉>100,和<0的情况
int score=90;
if (score>=80&&score <=100)
{
    Console .WriteLine("A");
}
else//这里的 else 和if看起来还是不清晰,
//可以直接写成else if。
{
    if (score>=60)
    {
        Console.WriteLine("B");
    }
    else
    {
        if (score>=40)
        {
            Console .WriteLine("c");
        }
    }

}

else if:else语句中包括一个if;

  • switch 语句 switch(数据类型)//不包括浮点类型 +代码块(标签+语句列表)

switch 标签有 case和default,以break;结束。 case:标签后面跟常量,可以多个标签写在一起来分类判断。
但是一旦标签后面跟了语句,就要以break;结束。

default 都不满足时执行。

Level myLevel=Level.High;//通过switch给不同枚举赋值。
switch (myLevel)
{
  case Level.High:
    Console.WriteLine("A");
    break;
  case Level.Mid:
    Console.WriteLine("B");
    break;
  case Level.Low:
    Console.WriteLine("C");
    break;
  default:
    break ;
}
Console.ReadKey();

enum Level
{
    High,
    Mid,
    Low,
}
  • try 语句——捕捉异常 try try +catch//可以有多个catch 但只能执行其中一个 //catch分为两类:一是通用

    ,二是捕捉专门的错误:

    1. ArgumentNullException结果为空值,
    2. FormatException格式有错误,
    3. OverflowException超出变量范围
    4. 如果不想解决就throw;给调用的人解决。

    try +catch+finally

    public int Add(string arg1,string arg2)
    {
          int a=0;
          int b=0;
          bool c=false;//用于给finally结果写一些注释
         try
         {
           a=int.Parse(arg1);
           b=Convert.ToInt32(arg2);
         }
         catch (ArgumentNullException )//捕捉专门的错误
         {
          
          Console.WriteLine("没有值");
          c=true;
         }
         catch (FormatException fe)//可以加变量直接告诉用户英文错误
         {
          
          Console.WriteLine(fe);
          c=true;
         }
          catch (OverflowException ofe)
         {
          
          throw ofe;//谁调用谁解决,会显示错误,不会解决。
          //ofe 去掉也可以识别。
          c=true;
         }
         finally
         {//都会执行
          //1,释放系统资源:例如数据库连接,如果出问题也会释放
          //2.程序的执行记录
          if (c)
          {
              Console.WriteLine("有问题的数字");
          }
          else
          {
              Console.WriteLine("正常数字");
          }
         }
    
          int result=a+b;
          return result ;
    }
    
4,迭代语句,循环语句iteration-statement

有四种不同的循环语句,while,do,for,foreach语句

  1. while 语句 执行0次或多次 while(循环条件)+循环体。
int score=0;
bool canContinue=true;
while(canContinue)
{
    Console.WriteLine("输入一个数字");
    string str1=Console .ReadLine();
    int x =int.Parse(str1);

    Console.WriteLine("输入一个数字");
    string str2=Console .ReadLine();
    int y =Convert.ToInt32(str2);
    int sum=x+y;
    if (sum==100)
    {
        score++;
        Console.WriteLine("答对!{0}+{1}={2},请继续",x ,y ,sum );
    }
    else
    {
        Console.WriteLine("失败!{0}+{1}={2}",x ,y ,sum );
        canContinue=false ;
        Console .WriteLine("你最后的分数{0}",score );
        Console .WriteLine("游戏结束" );
    }
  
    
}
  1. do 语句 执行一次或多次 do+循环体
    while+(循环条件)。
do
{
     Console.WriteLine("输入一个数字");
    string str1=Console .ReadLine();
    int x =int.Parse(str1);

    Console.WriteLine("输入一个数字");
    string str2=Console .ReadLine();
    int y =Convert.ToInt32(str2);
     sum=x+y;
    if (sum==100)
    {
        score++;
        Console.WriteLine("答对!{0}+{1}={2},请继续",x ,y ,sum );
    }
    else
    {
        Console.WriteLine("失败!{0}+{1}={2}",x ,y ,sum );
        
        Console .WriteLine("你最后的分数{0}",score );
        Console .WriteLine("游戏结束" );
    }
  
} while (sum==100);
  1. continue 语句和break语句
  • continue:放弃当前循环,开始新的循环。
        Console.WriteLine("输入一个数字");
      try
      {
         string str1=Console .ReadLine();
          x =int.Parse(str1);
      }
      catch 
      {
         Console .WriteLine("请正确的的数字");
         continue;//如果输入的数字错误,会 重新开始循环,所以要写在循环体中。
      }
     
    

break语句 :立刻结束循环,执行循环外部的代码。 string.Tolower();转为小写。

如果是多重迭代(循环)continue和break语句执行的是包裹住他们的循环语句。

  1. for语句 最好应用场景:计数循环,即循环的次数是固定的。

for (初始化器第一个执行,只执行一次;执行条件第二个执行;执行循环后执行(累积器)最后执行)+{第三个执行}

for 语句条件可以不写(;;),;一定要写,条件不写就是死循环。

for (int a = 1; a <=9; a++)//打印九九乘法表
{
    for (int b = 1; b <=a; b++)//a循环一次,b要完成全部的循环,a才开始下一次循环。
    {
        Console.Write("{0}*{1}={2}\t",a ,b ,a*b );//\t是制表符表示空格
    }//九九乘法表 基本格式是a*b=c;a在1-9,b在1-9,所以要循环9次从数字1开始,每行的b要小于等于a,
    Console.WriteLine();
}
  1. foreach 语句 集合遍历循环:枚举一个集合中的元素,访问集合每个元素。每访问一个元素就可以执行一次循环体。

    什么样的集合可以被遍历(迭代):拥有 IEnumerable(I代表结果)枚举接口。

    迭代器(enumerator):作用指向集合中的元素
    里面的方法 bool MoveNext();判断迭代器是否可以向后移动,可以true,object Current{get;} 当前正在访问的元素(当前的) void Reset();把迭代器拨回到集合的最开始,没有指向任何元素。

    int []intArray=new int []{1,2,3,4,5,6,7};
    IEnumerator enumerator =intArray.GetEnumerator();
    //把intArray,的迭代器给 迭代器接口,调用其中的方法
    
    while (enumerator.MoveNext())//判断迭代器是否可以向后移动
    

{ Console.WriteLine(enumerator.Current);//打印访问的元素 } enumerator.Reset();//迭代器拨回到集合的最开始// //才能开始新的迭代。

在list 中同理。

foreach 语句整合了这些部分 变成了语法糖衣

foreach(申明一个迭代变量(相当于迭代器用来指集合中的元素)`in`集合)+循环体(每拿到一个元素执行一次循环)
```csharp
int []intArray=new int []{1,2,3,4,5,6,7};
foreach (var current in intArray)
{
 Console .WriteLine(current);
}
//foreach 作用对集合里面的元素进行遍历。
  1. 跳转 语句 break,continue,goto ,return,throw goto 不是主流语句了
  • return 使用注意事项 一,尽早 return
    运行了return,后面代码不会执行,将可能有问题的地方return出去
    二,方法一定可以return,例如方法有if 还要有else

字段 ,属性,索引器,常量

表达数据。

字段

  • 字段(field)为对象或类型(类与结构体)存储数据的变量;D
  • 多个字段的组合可以表达类型(对象)当前的状态。
  • 字段是类型的成员,又叫成员变量
  • 字段-field表示的是数据存放的空间,每个字段都会在内存中占一块空间,字段组合在一起便是整个类储存的空间。
  • 帮助对象(实例)保存数据的叫实例字段,属于某个对象,表示对象当前的状态
  • 帮助数据类型保存数据的叫静态字段,属于某个数据类型,表示某个数据类型当前的状态。有static修饰符

一,字段声明:字段的名字一定要是名词。
要写在类体中。

可选(特性,修饰符可以多个,)+字段数据类型+变量声明器(变量名||变量名+初始化器);//不是语句

常用格式:访问级别+字段类型+变量名=0;/访问级别+静态类型+字段类型+变量名=0;
实例字段最好申明就初始化,静态字段是加载的时候初始化。

数据类型被加载的时候,调用的是静态构造器,数据类型只被调用一次,所以静态构造器只执行一次

class Student
{
    public Student ()//实例构造器
    {

    }
    static Student ()//静态构造器。不能赋值,只能改初始化
    {

    }
}

readonly(只读)//为实例(类型)初始化后就不能改变的值
//有点像静态字段,一旦初始化后,就不能改变。
虽然也有静态只读字段,但是功能和静态字段初始化一样。

 student stu1=new shtdent(1);//现在是为初始化器赋值
class student
{
  public readonly int ID;//只有一次赋值的机会
  public student(int id)//这是初始化器。
 {
   this.ID=id;
 }
}

属性

属性(property):访问对象或类型的特征,表示了一种特征。

属性是字段的自然扩展。反映现实世界对象的特征。

属性对外:不仅可以存储数据,还可以动态计数出来,
对内:保护字段不被污染。

属性有Get/set 语法演变过了。

get 用来获取数据,set用来设置数据。

Get/set设置保护字段:

  Studnet stu1=new Studnet();
  stu1.SetAge(200);//通过set设置数据
  int age =stu1.GetAge();//通过get 输出数据
  Console.WriteLine(age );
  class Studnet

{
    private int age;//私有修饰符,来修饰字段
    public int GetAge()//用get 获取字段
    {
        return this .age;
    }
    public void SetAge(int value)//通过设置,保护字段安全
    {
            if(value>=0&&value<=120)
            {
                this.age=value ;
            }
            else
            {
                Console.WriteLine("输入正确年龄1") ;
                //这只是控制台是有提示
                throw new Exception("输入正确年龄2");
                //这会有明显的报错
            }
    }
}

c# 属性是字段保护的语法糖衣,是字段的一种包装(wrapper)。

属性声明:
完整:可选的特性,属性的修饰符(public最常见),属性的数据类型(读取和输入值的类型) 名字 { get 和set} 代码提示 propfull 就有框架了,静态属性声明就是加了static修饰符,用类表示类型当前的状态。

永远使用属性(不用字段)向外暴露数据,字段永远都是private或protected

Studnet stu1=new Studnet();
stu1 .Age=100;//不用选择get/set,都有Age方法代替。
Console.WriteLine(stu1.Age );
class Studnet
{
  
  private int age;
  public int Age
  {
      get { return age; }
      set {
          if (value>=10&&value<=120)//这的value是默认值,自动有一个输入参数。
          {
              age =value;
          }
          else
          {
              throw new Exception();
          }
          }
  }
  
}

简略声明:和字段的功能是一样的,用来传递数据。

public int Age { get; set; }

只读属性,和只写属性。

只读属性:只保留get,定义了只读属性,就只能通过该属性取值,而不能赋值。

只写属性: 在set前加 private,可以在类的内部访问。

动态计算值:用来判断输入值是否满足条件

//方法一,比较get ,通过写一个判断的方法,来查看结果是否符合。
//只有主动调用才开始计算。
Studnet stu1=new Studnet();
stu1.Age=12;
Console.WriteLine(stu1.CanWork);//通过CanWork来判断结果是否符合。
class Studnet
{
    private int age;
    public int Age { get; set; }
    public bool CanWork//没有封装一个字段,随着外边的值的变换,而变换
    {
      get //判断得到的数是否满足条件。
     {
        if (this.age>=16)
        {
            return true ;
        }
        else
        {
            return false;
        }
     }
    }
    
}

//方法二,比较set,每次赋值的时候,都进行判断
Studnet stu1=new Studnet();
stu1.Age=12;
Console.WriteLine(stu1.canWork);
class Studnet
{
    private int age;

    public int Age {
        get {return age ;}

        set //在被赋值时,调用
        {
            age =value ;
            this .CalcumateCanWork();//调用方法,判断被赋的值,是否满足条件
        }
    }
    //写 set 的判断条件
    private bool canWork;//申明一个 字段,作为判断的参数
    
    public bool CanWork  //为字段加一个只读的属性,不能修改,//这里删除也可以运行。
    {
        get {return canWork;} 
    }
    private void CalcumateCanWork()//申明方法。
    {
        if (this.age>=16)
        {
            this .canWork=true ;

        }
        else 
        {
             this .canWork=false ;
        }
    }
}

索引器(indexer)?

用来检索一个集合。

声明的时候用index 快捷键

用在集合类型里面,

//用在非集合中,很少用
student stu=new student();
stu ["Math"]=90;
var mathScore=stu["Math"];
Console .WriteLine(mathScore);
class student
{

    //检索学生成绩
    //申明一个字典作为学生成绩
    private Dictionary<string ,int >scoreDictionary=new Dictionary<string,int >();
    //添加索引器
    public int? this[string subject] //public +数据类型+this
    {
        get//那科目 向字典里面查 ,有就返回,没有就null
        {
            if (this.scoreDictionary.ContainsKey(subject))//学科在不在字典目录里面
            {
                return this .scoreDictionary[subject ];
            }
            else
            {
                return null;
            }
        }
        set
        {
            //判断是否为空
            if (value.HasValue==false)//没有值的意思
            {
                throw new Exception("成绩为空");
            }
         if (this.scoreDictionary.ContainsKey(subject))//学科在不在字典目录里面
            {
                this.scoreDictionary[subject]=value.Value;//代表学科赋进来的值
                //可空类型 value.value 才是可空类型
            }
            else/
            {
                this.scoreDictionary.Add(subject,value.Value);
            }
        }
    }
}

常量(constant)

常量:表示常量值,在编译的时候替换常量符号如pi;
存储不变的值,可以提高运行效率。

常量只能是一个基本数据类型

实例常量是只读实例字段,本质还是变量

局部常量是在Main 类中

声明成员常量

class student
{
    public const string WebMocc="https://www.icourse163.org/";
}

只读类型运用场景

  • 常量:为了提高程序可读性和执行效率。
  • 只读字段:为了防止对象的值被修改,加readonly(只读)修饰符 ,只能初始化一次
  • 只读属性:想要暴露不允许修改的数据,只有get 没有set。功能与常量相识,但性能没有常量高。
  • 静态只读字段:补充常量声明(类/自定义结构体)时用。

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

传值参数

传值参数——值类型

传值参数也叫值参数(形参):声明的时候不带任何修饰符的参数。
实参的副本,永远不会影响实参的值。(像是用实参的值,创建了一个新的变量)
为当前方法创建一个局部变量,(看成实参的副本,改变值参数,不会影响实参)
初始值:在调用方法时,赋予的值(实参)
在方法中改变了传值参数,不会影响变量的值(方法调用时候赋予的值)

student stu =new student ();
int y=100;//实参
stu .AddOne(y);//把实参给到传值参数(形参)。并进行方法调用,
//最后结果是 实参在方法体中副本的值。为101,
Console.WriteLine(y );//但是没有改变实参的值
class student//(形参在计算)
{
    public void AddOne(int x )//没有修饰符,值类型。(形参)
    //为实参 创建一个副本,来参与方法运算,不改变实参的值
    {
        x+=1;
        Console.WriteLine(x );
    }
}

传值参数——引用类型

引用类型的变量存储的是一个地址,
看起来比引用传值参数更复杂,本质还是一样:传值参数——引用类型(形参)在方法中改变了传值参数(实参),不会影响变量(实参)的值
引用类型传值参数(形参)是在改变地址,当引用类型传入方法中,会创建一个新的地址,不会改变(实参)原来地址的内容。

实际操作中没什么用

student stu= new student (){Name="Tim"};
//为 Name 赋值“Tim”
SomeMethod(stu );//同 过someMethod 方法运算后的stu(形参)。与原来的stu(实参)的地址已经不一样了Tom
//这个里面的名字一定要一样,表示传入的参数
Console.WriteLine(stu.Name);//但是没有改变(实参)原来的地址,原来地址还是Tim

static void SomeMethod (student stu )
//这的名字是这个引用类型(形参)在方法里面叫什么,作用域是这个方法 可以不和 实例 stu(实参)一样。
//static void SomeMethod (student ent  )也可以
// 把新将的实例 (实参)stu (引用变量)的值传入 SomeMethod方法中(变为形参)
{
    stu =new student (){Name="Tom" };
    //给引用类型(形参)创建新的实例,并赋值Tom(这时,已经创建新的地址给到形参了,和刚刚传入的引用变量(实参)无关了)

    //只操作对象,不创建新的对象,形参其实还是会创建一个副本的,与实参的内存地址不一样,但其中存储的地址是一样的,是实例在堆中的地址。
    //stu.Name="Tom";如果没有创建新的实例,值是会被改变的 
    //这是副作用,side-effect 要避免。
    Console .WriteLine(stu.Name);
}
class student
{
    public string Name {get;set;}//创建一个属性,可在方法外访问。

}

如果结果一样,可以用stu(变量名).GetHashCode();每个变量都有自己独一无二的HashCode,通过这个,看变量是否真的一样。

引用参数

引用参数>值类型

用ref修饰符申明的参数(形参)

不创建副本,存储位置就是传进来的参数(就是实参在方法里面计算), 实参要明确赋值才能传进来,

在调用时候也要加上ref关键字。

//有点像传值参数——引用类型(副作用),只操作对象,不创建新的对象。//本质是不同的后面会解释
//显性的告诉编译器,有副作用,利用副作用。要直接计算实参。
int y=1;
SomeMethod(ref y);//使用时,也要加上ref
Console.WriteLine(y);

static void SomeMethod (ref int x )//声明时候要加 ref(实参与形参一样)
{
   x=x +100;//等同于自己将实参传入并计算。
}

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

引用类型(形参)在方法中创建一个新的内存地址,引用类型(形参)实际上是对引用(实参)直接操作,引用(实参)也变成一个新的内存地址。

如果没有创建引用类型(形参)新的实例,引用类型(形参)改变了引用(实参)的值==引用值类型(形参)效果。

引用类型改变了引用(实参)的值==引用值类型效果,这看起来很像传值参数——引用类型的副作用。
但是不一样的传值参数——引用类型,形参如果没有新建实例,也是会创建一个副本的,与实参的内存地址不一样,但其中存储的地址是一样的,是实例在堆中的地址,而引用参数,实参和形参指向的内存地址是同一个,存储的是对象在堆中的地址。

Student otherStu=new Student(){Name="Tim"};
Console .WriteLine("HashCode={0},Name={1}",otherStu.GetHashCode,otherStu.Name);
//现在 otherstu 有一个地址
Console .WriteLine("******************************");
SomeMethod(ref otherStu);//通过SomeMethod方法,otherStu新建立一个地址
Console .WriteLine("HashCode={0},Name={1}",otherStu.GetHashCode,otherStu.Name);
//原来的地址就变为了新建的地址。也就是方法新建的地址

static void SomeMethod (ref Student stu  )//声明时候要加 ref,这里形参和实参是一样的
//Student是类 引用类型,变量名为stu
//这个方法是为引用类型创建一个新的地址
{
   stu =new Student(){Name="Tom"};//为引用变量新建一个实例并赋值
   Console .WriteLine("HashCode={0},Name={1}",stu.GetHashCode,stu.Name);
}
class Student
{
    public string Name{get;set;}
}

输出参数

在输出阶段,使用out修饰符声明的参数(形参),
输出形参不会创建新的存储位置,与实参是同一个内存地址。
输出参数传入方法中参数没有要求要赋值,但是方法中一定要为输出参数赋值。
输出参数可以出现多种返回值情况。

//调用输出参数
Console .WriteLine("输入一个数字");
string arg1=Console .ReadLine();
double x=0;
bool b1=double.TryParse(arg1,out x );//try parse就是一个输出参数,
//有多个返回值,如果成功就输出x,如果失败就输出bool类型的值,并输出默认值0,因为输出参数要赋值。
if (b1==false)
{
    Console .WriteLine("输出错误");
    
}
 Console .WriteLine(b1 );//成功为true,失败为false。
 Console .WriteLine(x );//如果成功x值为输出参数,失败为了0。

输出参数>值参数

double x=100;
bool b=DoubleParser.TryParse("ab",out x );//输出参数要给一个out.
if (b==true)
{
    Console .WriteLine(x +1);//值为方法的输出参数+1
}
else
{
    Console.WriteLine(x );
    //如果解析失败,输入的参数,会被方法的输出参数覆盖掉,为默认值0;
}
class DoubleParser
{
    public static bool TryParse(string input ,out double result)
    {
        try
        {
            result=double .Parse(input );
            return true;//没有异常return true
        }
        catch 
        {
            result=0;
            return false;//要为输出参数赋值,不然会报错
            throw;
        }
    }
}

输出参数>引用类型

//就是你写了一个类,想要为它,输出结果做各种条件限制,可以输出多个参数
Student stu=null;//为student创建一个空对象
bool b=StudentSchool.create("Tom",25,out stu );
//用空对象来接受 StudentSchool类中方法的结果,
if (b=true)
{
    Console.WriteLine("Name is{0},age is{1}",stu.Name,stu.Age );
}

class Student//写了一个学生的类,有 名字和年龄属性
{
    public int Age{get;set;}
    public string Name{get;set;}
}
class StudentSchool//对学生的属性进行限制
{
    // 可以同时传入 年龄和名字进行判断,限制。并且一起输出,还改变了原来的引用类型
    public static bool create (string stuName,int stuAge,out Student result)
    {
        result=null;
        if (string.IsNullOrEmpty(stuName))
        {
            return false;
        }
         if (stuAge<20||stuAge>80)
        {
            return false;
        }
        //用两个if条件,来判断传入值是否正确
        result=new Student(){Name=stuName,Age=stuAge};
        //为student类创建一个名字为结果的实例,并将限制过的结果传给 student类
        return true;
    }
}

数组参数

  • 必须是形参列表中的最后一个,由params修饰
//params==int [] myIntArray=new int []{1,2,3};
//如果没有加 params关键字 就要先声明一个数组,并传入方法中
//int [] myIntArray=new int []{1,2,3};
//int result=CalculateSum(myIntArray);
int result=CalculateSum(1,2,3);
//在方法中加了 params 关键字 就可以直接把数组元素作为参数输入
Console.WriteLine(result);
static int CalculateSum(params int[] intArray)
{
    int sum =0;
    //变量数组中的元素。
    foreach (var item in intArray)
    {
        sum +=item;
    }
    return sum ;
}
Console.WriteLine("{0}+{1}={2}",1,2,3);
//这也是数组参数的表现。[1,2,3]是数组的简写。

//利用标点符号直接创建,字符串数组
string str="li,tom;ai,ou";
string [] result = str.Split(',',';',',');
foreach (var enumerator  in result)
//复习foreach,var变量跟是迭代器用来指数组中的元素,in 确定是那个数组
//每迭代一次执行一次循环。
{
    Console.WriteLine(enumerator);
}

具名参数

参数位置不受限制

调用方法的时候,传入参数是有名字的

student(age:18,name:"too");//具名调用就是在调用的时候 是变量名+:结果。
//可读性更高
static void student(string name,int age)
{
    Console.WriteLine("stu name{0},age{1}",name ,age );
}
Console.ReadKey();

可选参数

参数因为有默认值,调用参数可写,可不写

student();
//不建议使用,
static void student(string name="额",int age=18)
{
    Console.WriteLine("stu name{0},age{1}",name ,age );
}

扩展方法(this参数)

什么是扩展方法

  • 方法必须是公有,静态的,有 public static修饰符的
  • 形参 列表中第一个,有this修饰符
  • 必须由一个静态类(一般为SomeTypeExtension)来统一收纳(SomeType类型的扩展方法)
  • LINQ方法
  • 可以理解为对默认方法的重载。
double x=3.145926;
double y=Math .Round(x,4);
//通过math类中的round方法可以实现对于x小数点后四位并且四舍五入
//如果直接通过double直接进行四舍五入。
double z=x.Round(4);
Console.WriteLine(y );
Console.WriteLine(z );//结果是一样的。
Console.ReadKey();

 static class DoubleExtension//放在一个静态类,名字要加上Extension
{
    //方法名字要公有,静态,
    public static double Round(this double intput ,int digits )
    //通过this 方法将double方法重载了一个新的方法。
    //有一个 double类型的输入,和一个int类型,数字代表小数点后几位。
    {
        double result=Math.Round(intput ,digits);
        return result ;

    }
}

在vscode dotnet6.0 默认引用了 Linq;在linq中有很多扩展方法。

List<int >mylist=new List<int>(){11,12,13};
bool result=mylist.All(i=>i>10);//扩展方法。list里面元素是否都大于10.
Console.WriteLine(result);

参数的使用场景

  • 传值参数:参数默认传递方式,参数是实参的副本,不会影响实参
  • 输出参数:用于除返回值外还需要输出的场景,是对输出结果的限制。
  • 引用参数:用于需要修改实际参数值的场景
  • 数组参数:用于简化方法的调用
  • 具名参数:提高可读性
  • 可选参数:参数有默认值
  • 扩展方法(this参数):为目标类型“追加”方法,对方法的重载。

委托

什么是委托

委托(delegate):是函数指针的“升级版”

通过函数名字调用函数:为直接调用。 指针,间接调用.效果和直接调用一样。

int x=100;int y=200;
typedef int(*Calc)(int a,int b);//定义指针类型
Calc fucPoint1=&Add;//声明指针变量
z=fucPoint1(x,y);//调用方法

地址

  • 变量(数据):以变量名对应的内存地址为起点的一段内存,在这个内存中存储的就是变量的数据。内存的大小由变量的数据类型决定的
  • 函数(算法):以函数名对应的内存地址为起点的一段内存,在这个内存中存储的是一组机器语言的指令。cup就是按照函数内存中的指令一条一条执行完成算法的。

直接调用与间接调用的本质

  • 直接调用:通过函数名调用函数,cup通过函数名字直接找到内存的地址,然后逐条执行指令,执行完了就返回到调用者那去,继续向下执行。
  • 间接调用:通过指向某个函数的函数指针来调用函数。函数指针是变量,里面存储着函数名字的地址,cpu通过函数指针,找到函数名字的地址,在通过函数名字调用方法,并逐条执行。执行完了就返回到调用者那去,继续向下执行。
  • 感觉像是给函数名字取了个外号,通过外号调用了函数方法。
//函数指针的升级版,按照一定约束指向目标方法,完成方法的间接调用。
Calculator calculator=new Calculator();
calculator.Report();
//直接调用
Action action =new Action(calculator.Report);
//Action 类型委托是要一个 有返回值,参数列表为空的方法
//只要方法名。
action.Invoke();//invoke(调用),就可以用方法了
//还可以简写
action();
//func委托 有返回类型,前两个是输入值类型,后一个int是返回值类型//func有返回值,返回值是最后一位。
Func<int,int,int >func1=new Func<int, int, int>(calculator.Add);
Func<int,int,int >func2=new Func<int, int, int>(calculator.Sub);
int x=100;
int y=30;
int z=0;
z=func1.Invoke(x,y );
//也可以简写
Console.WriteLine(z);
z=func1(x,y );
Console.WriteLine(z);
z =func2.Invoke(x,y );
Console.WriteLine(z);
Console.ReadKey();

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 )
    {
        int result=a-b;
        return result;
    }
}

自定义委托

委托是类(class)的一种,类是数据类型,所以委托也是
//VSCode 如何替换 ctrl +H,【Ctrl键】+【Alt键】+【Enter键】全部替换。

注意事项:要声明在命名空间中。 如果写在类里面,就变成嵌套类了,调用是类名.+委托。

//委托和封装的方法必须要“类型兼容”
Calculator calculator=new Calculator();
calc calc1=new calc(calculator.Add);
//创建委托,传入方法。
calc calc2=new calc(calculator.Div);
calc calc3=new calc(calculator.Mul);
calc calc4=new calc(calculator.Sub);
double a=100;
double b=300;
double c=0;
c=calc1(a,b);
Console.WriteLine(c);
c=calc2.Invoke(a,b);
Console.WriteLine(c);
public delegate double calc(double x,double y);
//和类一样 声明在名称空间,用delegate 修饰 +返回值类型+返回值名字+目标方法列表
class Calculator
{
    public double  Add(double a,double b )
    {
        return a+b;
    }
      public double Sub(double a,double b )
    {
        double result=a-b;
        return result;
    }
     public double Mul(double a,double b )
    {
        return a*b;
    } public double Div (double a,double b )
    {
        return a/b;
    }
}

委托的一般使用

把委托当作方法的参数传入到另一个方法(这里只main class)

  • 模板方法,写了一个方法,借用委托,给指定的外部方法调用,产生结果
  1. 在写主要程序时候 有一个填空题目,空白处用委托类型参数填补,委托调用外部方法,这个方法一般有返回值的。拿到返回值,继续执行下面逻辑。
  2. 常在代码中部
  • 回调(callback)方法,某个方法可以调用,也可以不调用,需要时候在调用,调用指定的外部方法。
  1. 是动态的调用,选择多个解决方法。
  2. 把委托类型参数,给主调类型参数中去,委托类型中封装一个被回调的方法,像一个备胎,主调方法更具需求调用备胎。
  3. 像一条流水线,在主调方法执行完了,在决定是否调用委托方法(备胎)继续向下执行,常在代码末尾,委托没有返回值。
//用委托类型的参数封装一个外部的方法,把方法传入内部进行间接调用。
//通过实例 开始生产产品
PorductFactory porductFactory=new PorductFactory();
//使用模板方法,Func<Product>getProduct,接受委托类型的参数,传入变量
//创建 披萨,和小汽车的委托类型,传入包装产品方法中。
Func<Product>fuc1=new Func<Product>(porductFactory.MakePizza);
Func<Product>fuc2=new Func<Product>(porductFactory.MakeToyCar);
WrapFactory wrapFactory=new WrapFactory();//包装产品方法创建实例
//开始调用包装产品的方法:需要一个委托类型,就调用前面的披萨和小汽车。
Logger logger =new Logger();
Action<Product>log=new Action<Product>(logger.Log);
Box box1=wrapFactory.WrapProduct(fuc1,log);//方法的返回值是box,public Box WrapProduct
Box box2=wrapFactory.WrapProduct(fuc2,log);
//使用用Box变量接收。

Console.WriteLine(box1.product.Name);
Console.WriteLine(box2.product.Name);
Console.ReadKey();
class Product
{
    public string Name{get;set;}
    //回调方法
    public double price {get;set;}
}
class Logger
//记录程序的运行状态。
{
    //只是计入没有返回值。
    //计入 Product product方法什么时候创建的
    public void Log(Product product)
    {
        Console.WriteLine("Product'{0}'created at {1}.Price is {2}",
        product.Name,DateTime.Now,product.price);
        //utcNow是没有时区的时间。
    }
}
class Box
{
    public Product product {get;set;}
}
class WrapFactory
//模板方法
//好处是只用 改class PorductFactory类中的方法。
{
    public Box WrapProduct(Func<Product>getProduct,Action<Product>logCallback)
   {
    //传入一个委托类,通过委托得到一个值,赋给一个box类作为返回值
    Box box =new Box ();//第一步新建一个 box
    //第二步,获取一个产品,通过委托的方法间接获取
    //这里的委托指的是对 PorductFactory类中方法的委托。来间接调用
    //Func<Product>fuc1=new Func<Product>(porductFactory.MakePizza);
    //这里的委托也是唯一可以在外部修改的部分,可以指定不同的方法。
    Product product =getProduct.Invoke();//返回值是product
    
    //这里是回调方法,因为没有返回值使用用 action 委托。
    //用于计入产品价格
    if (product.price>=50)
    {
        logCallback(product);
    }
    box.product=product;
    //最后把产品放到 box中。
    return box;
   }
}
class PorductFactory
//生成 各种产品的。
{
    public Product MakePizza()
    //生产一个 pizza
    {
        Product product1 =new Product();
        product1.Name="Pizza";
        product1.price=12;
        return product1;

    }
    public Product MakeToyCar()
    {
        Product product2=new Product();
        product2.Name="ToyCar";
        product2.price=100;
        return product2;
    }
}

委托的注意事项

  • 缺点一:这是一种方法级别的紧耦合,在使用中要慎之又慎
  • 缺点二:可读性下降,debug难度增加
  • 缺点三:把委托回调,异步调用和多线程纠缠在一起,会使代码难以阅读
  • 缺点四:委托使用不当有可能造成内存泄漏和程度性能下降。委托是要类中的方法,所以要储存类的地址。程序释放内存可能泄漏。

委托的高级使用

  1. 多播(multicast)委托:一个委托内封装不止一个方法。 用+= 连接不同的委托,是按照同步调用是顺序
  2. 隐式异步调用
    • 同步调用是:程序运行是在一个进程(process)依次执行完毕,是单线程 同步调用
    • 异步调用是:程序在一个进程是出现一个或多个线程(thread),多线程可以同时执行, 这里委托有方法可以调用隐式异步,但是没有学习多线程控制,意义不大

异步调用

最后是用接口(interface)代替委托,提高代码的可读性。

事件

初步了解事件

事件(event):能够发生的什么事情。
在c#中,是类型的成员,
事件参数(eventArgs):事件发出的消息,
只有事件,没有事件参数的事件:无需额外消息,事件本身就是消息了。
处理事件:根据事件,采取行动,由event Handler事件处理器,来处理事件。

功能:使对象或类具有通知(notification)能力的成员。
事件功能=通知+可选的事件参数(详细信息)
用法:用于对象或类之间的动作协调与信息传递(消息推送);
某对象拥有什么事件:指的是对象可以通过事件发信息给关心这个事件的别的对象,别的对象得到信息,做出响应。程序运转起来了。

事件模型/发生响应模型(event model):事件的固定模式 事件模型组成:发生>响应的5个部分:孩子 饿了 我 做法,其中暗含着:孩子要和我有关系我才给孩子做饭(被叫做“订阅”关系),使用是5部分。 事件模型构建运作:发生>响应5个步骤:1.我要有一个事件,2.一个或一群人关心这个事件,3.我这个事件发生了,4.关心这个事件的人会被依次通知到,通知的次序是订阅事件的次序。5,关心事件的人拿到事件信息(事件数据,事件参数,通知)做出响应(处理事件)。

事件术语约定

  1. 事件的订阅者
  • 事件消息的接收者
  • 事件的响应者
  • 事件的处理者
  • 被事件所通知的对象
  1. 事件参数
    • 事件信息
    • 事件消息
    • 事件数据

事件主要是用于开发桌面,手机客户端,与用户打交道。用户操作数据,得到新结果,完成一次事件。
叫做事件驱动的程序。

设计模式:MVC,MVP,MVVM,事件模式。

事件详解

事件模型五个组成部分详解

  1. 事件的拥有者(event source,对象)
    谁拥有这个事件,事件的源头,一个对象(一个类);
  2. 事件成员(event ,成员) 事件不会主动发生,需要被拥有者的某些逻辑出发,才能发挥通知的作用。
    在window系统中,鼠标会发出电信号,在屏幕移动,用户点击按钮会触发按钮的事件。
  3. 事件的响应者(event subscriber,对象)
    订阅了事件的一个对象或是类。
  4. 事件处理器(event handler,成员)
    是事件响应者的一个方法成员,本质上是一个回调方法。
  5. 事件订阅
    解决了3个问题:一,事件发生的时候事件拥有者会通知谁?订阅事件的对象。
    二,拿什么样的方法或事件处理器才能处理这个事件?c#会进行严格的类型检测,c#中规定:事件与事件处理器之间要遵守同一个约定,相互匹配。约定就是(委托)。
    三,事件的响应者具体拿那个方法处理这个事件?在订阅的时候就要告诉事件,要拿那个方法处理事件

新版本的form有不同的框架,这些代码在新版本中运行不了,以了解事件为主。

//事件拥有者,和事件响应者,是两个不一样的对象。
static void Main()
    {
            Form form =new Form ();//这个form是global using global::System.Windows.Forms;下的form,
            //事件的拥有者 打开一个窗体
            Controller controller =new Controller(form);
            //事件的响应者
            form.ShowDialog();
    }
    class Controller
{
     private Form form1;//用来判断是不是想要的事件。
     public Controller(Form form2 )
      //设置 controller(控制器)的默认值
     {
            if (form2!=null)//如果 传进来的事件不为空,就开始执行事件
            {
                this.form1=form2;//把传进来的事件给到 内部进行处理
                this.form1 .Click+=this.FormClicked;
                // click事件成员(点击) 调用事件中的点击,一旦点击开始处理事件。
                // +=事件订阅
                //事件处理器FormClicked
            }
     }
     private void FormClicked(object sender,EventArgs e )
     //具体如何处理
     //版本不一样现在运行不了。
    {
        this.form1.Text=DateTime.Now.ToString();
    }
}    

事件的拥有者同时也是事件的响应者
需要类中带有事件才能使用。
form是window 自带的类不能修改,需要用派生,继承的知识

static void Main()
    {
            MyForm form =new MyForm ();
            //既是事件的拥有者也是事件的响应者 
            form.Click+=form.FormClicked;
            //click(事件成员,事件触发条件)
            //+=订阅事件
            //FormClicked 事件处理
            form.ShowDialog();
        

    }    
 

class MyForm: Form//继承,派生 
//将window.form 作为父类继承其中的方法。
{
    internal void FormClicked(object sender,EventArgs e)
    {
        this.Text=DateTime.Now.ToString();
    }
}

事件拥有者是事件响应者的一个字段成员。
事件响应者用直接的方法订阅 事件字段成员。

{
static void Main()
    {
            MyForm form =new MyForm ();
            //事件响应者
            Application.Run(form);

    }    
class MyForm: Form//继承,派生 
//将window.form 作为父类继承其中的方法。
{
    private TextBox texbox ;
    //事件处理器中运用
    private Button button ;
    //事件拥有者
    public MyForm()
    {
        this.texbox=new TextBox();
        this.button=new Button();
        this.Controls.Add(this.button);
        this.Controls.Add(this.texbox);
        this .button.Click+=this.ButtonClicked;
            //click(事件成员,事件触发条件)
            //+=订阅事件
            //buttonClicked 事件处理器
    }
    private void ButtonClicked(object sender ,EventArgs e )
    {
        this .texbox.Text="Hello,world";
        //促发事件 点击 button,会显示文字
    }

}
}

声明自定义事件

事件是基于委托的:

  1. 事件需要委托类型来约束:约束事件能发什么样的消息给事件响应者,也规定了事件响应者能收到什么样的事件消息。
    所以事件处理器 要与这个约束匹配是,才能订阅事件。

  2. 事件处理器可以匹配事件后,需要保存记录事件处理器:需要委托类型的实例。

    以下面客人点单的案例为例: 事件的拥有者是:客人 customer 事件成员(触发事件的条件):order 事件响应者:服务员,waiter:

完整版的事件声明
  Customer customer=new Customer();
  //--事件拥有者--
  Waiter waiter=new Waiter();
  //--事件响应者--
  customer.Order+=waiter.Action;
  //事件触发条件customer.Order,订阅事件+=,waiter.Action事件处理器。
  customer.Action();
  customer.PayTheBill();
  Console.ReadKey();
  public class Customer
    //--事件拥有者--
    {
         public void PayTheBill()
        //事件成员,事件触发条件(买单)
        {
            Console.WriteLine("给你 ${0}",this.Bill );
        } 
        private OrderEventHandler oEH;
        //声明一个点菜的事件。
        public event OrderEventHandler Order 
        //声明一个事件。
        {
            //添加事件处理器
            add{
                this.oEH +=value;
                //value是默认值 
            }
            //事件处理器的移除器
            remove
            {
                this.oEH-=value;
            }
        }
        public double Bill{get;set;}
        public void WalkIn()
        {
            Console .WriteLine("customer Walk into the restaurant");
        }
         public void sitDown()
        {
            Console .WriteLine("sitDown");
        }
         public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("let me think...");
                Thread.Sleep(1000);
            }
            //触发了点菜事件。
            //事件成员--触发事件--
            if (oEH !=null )
            {
                OrderEventArgs a=new OrderEventArgs();
                a.DishName="pizza";
                a.size="large";
                this.oEH.Invoke(this,a );
               
            }
        }     
       public void Action() 
       {
         Console.ReadLine();
         this.WalkIn();
         this.sitDown();
         this.Think ();
       }
    }
    public class OrderEventArgs:EventArgs
    //window 提供的一个父类,
    //EventArgs(事件参数)传递数据,属于事件成员
    {
        public string DishName{get;set;}= string.Empty;//点什么菜
        public string size {get;set;}= string.Empty;
    }
    public delegate void OrderEventHandler(Customer customer ,OrderEventArgs e );
    //先声明一个服务员的委托 传入(顾客消息,一些店内信息的类,用于传递数据。)
    //eventhandler 表示是为事件声明的委托。事件通过委托类型来传递消息的。
     
    public class Waiter 
    //--事件响应者 --
    {
        public  void Action(Customer customer,OrderEventArgs e )
        {
            //事件处理器。
            //顾客触发点菜的事件,服务员,需要回应 。
            Console .WriteLine("我想吃{0}",e.DishName);
            //如顾客想吃 什么,告诉顾客价格。
            double price=10;
            switch (e.size)
            {
                case"small":
                price=price*0.5;
                break ;
                case"large":
                price=price*1.5;
                break ;
                default:
                break;
            }
            customer.Bill+=price ;
            Console .WriteLine("{0}的价格是{1}",e.DishName,customer.Bill);
        }
    }

事件简化申明:

private OrderEventHandler oEH;
        //声明一个点菜的事件。
        public event OrderEventHandler Order 
        //声明一个事件。
        {
            //添加事件处理器
            add{
                this.oEH +=value;
                //value是默认值 
            }
            //事件处理器的移除器
            remove
            {
                this.oEH-=value;
            }
        }
//********************简化后:
 //事件的简化声明 event+那种委托类型约束事件+事件的名字
        public event OrderEventHandler Order ;
        //看上去 少了一个委托类型的存储,

委托类型可能会在类的外部滥用,所以用事件来限制委托。 事件本质是委托字段的包装器,(像是字段和属性的关系),限制委托字段的访问,

  • 一种 封装(encapsulation)的概念,就是隐藏一些功能。
  • 事件对外界隐藏委托实例的大部分功能(如:invoke,?null),仅暴露添加/移除事件处理器的功能

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

  • 用于声明A事件的委托,一般命名为AEventHandler(除非是一个非常通用的事件约束) 通用事件委托类型介绍:

public delegate void OrderEventHandler(Customer customer ,OrderEventArgs e );//是个类,要写在命名空间里面
//可以写成:
public event EventHandler Order;
//要写在 事件拥有者里面 ,是一个事件里面封装了一个委托,
//这一句声明,即有事件,又包含了一个委托。
public delegate void EventHandler(object? sender, EventArgs e);
//就是被封装的委托,
//在委托类型要改变,需要用as转换。
public  void Action(object ? customer2 ,EventArgs e )
        {
            //需要做类型转换
            Customer customer =customer2 as Customer;
            OrderEventArgs order=e as OrderEventArgs;
            
            Console .WriteLine("我想吃{0}",order .DishName);
            //如顾客想吃 什么,告诉顾客价格。
            double price=10;
            switch (order .size)
            {
                case"small":
                price=price*0.5;
                break ;
                case"large":
                price=price*1.5;
                break ;
                default:
                break;
            }
            customer.Bill+=price ;
            Console .WriteLine("{0}的价格是{1}",order.DishName,customer.Bill);
        }

AEventHandler的委托,一般有两个参数(win32,演化历史悠久)

  • 第一个是object类型,名字为sander,实际上就是事件拥有者的意思,source.
  • 第二个是EventArgs类的派生类,一般为AEventArgs,参数名为 e,就是事件所需的参数。

触发A事件的命名方法(触发一定要是事件的拥有者去做),OnA,即因何引发事出有因

  • 一般情况下用于触发事件的方法,用protected,不能为public。

事件的命名约定

  • 带有时态的动词,或者动词短语(要带有正确的时态)
  • 事件拥有者"正在做"什么事情,用进行时;事件拥有者"做完了"什么事情用完成时。
完整版的简化事件声明
Customer customer=new Customer();
//--事件拥有者--
Waiter waiter=new Waiter();
//--事件响应者--
customer.Order+=waiter.Action;
//事件触发条件customer.Order,订阅事件+=,waiter.Action事件处理器。
customer.Action();
customer.PayTheBill();
Console.ReadKey();
public class Customer
    //--事件拥有者--
    {
         public event EventHandler Order;
         public void PayTheBill()
        //事件成员,事件触发条件(买单)
        {
            Console.WriteLine("给你 ${0}",this.Bill );
        } 
        
        public double Bill{get;set;}
        public void WalkIn()
        {
            Console .WriteLine("customer Walk into the restaurant");
        }
         public void sitDown()
        {
            Console .WriteLine("sitDown");
        }
         public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("let me think...");
                Thread.Sleep(1000);
            }
            this.OnOrder("pizza","large");
        }     
        protected void OnOrder(string disName ,string size ) 
        {
            //触发了点菜事件。
            //事件成员--触发事件--记得判断委托是否为空。
            if (this.Order!=null )
            {
                OrderEventArgs e =new OrderEventArgs();
                e.DishName=disName;
                e.size=size ;
                this.Order.Invoke(this,e );
               
            }
        }
       public void Action() 
       {
         Console.ReadLine();
         this.WalkIn();
         this.sitDown();
         this.Think ();
       }
    }
    public class OrderEventArgs:EventArgs
    //window 提供的一个父类,
    //EventArgs(事件参数)传递数据,属于事件成员
    {
        public string DishName{get;set;}= string.Empty;//点什么菜
        public string size {get;set;}= string.Empty;
    }
    //public delegate void OrderEventHandler(Customer customer ,OrderEventArgs e );
    //先声明一个服务员的委托 传入(顾客消息,一些店内信息的类,用于传递数据。)
    //eventhandler 表示是为事件声明的委托。事件通过委托类型来传递消息的。
    
    public class Waiter 
    //--事件响应者 --
    {
        
        public  void Action(object ? customer2 ,EventArgs e )
        {
            //需要做类型转换
            Customer customer =customer2 as Customer;
            OrderEventArgs order=e as OrderEventArgs;
            
            Console .WriteLine("我想吃{0}",order .DishName);
            //如顾客想吃 什么,告诉顾客价格。
            double price=10;
            switch (order .size)
            {
                case"small":
                price=price*0.5;
                break ;
                case"large":
                price=price*1.5;
                break ;
                default:
                break;
            }
            customer.Bill+=price ;
            Console .WriteLine("{0}的价格是{1}",order.DishName,customer.Bill);
        }
    }

事件与委托的关系

事件不是委托声明的字段,不过事件简化声明格式像是一个字段,是委托事件的"蒙蔽"mask.

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

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

对比事件与属性
都是包装器,用于保护包装的东西,不是东西本身。

什么是“类”:

  • 是一种数据结构(data structure):是对现实世界抽象载体,抽象结果。
  • 是一种数据类型:类是一种引用类型,每一个类都是一个自定义的类型。可以用类:声明变量,创建实例(实例的模板)。
  • 代表现实世界中的“种类”:

构造器与析构器

  • 实例
    实例构造器的两种方式。
    ()+{}初始化器(可以为默认构造器赋值)。
    如在类中声明了ctor(构造函数;构造器),那么默认构造器就会失效,没有了{}初始化器
    直接在()传入参数赋值就可以了完成实例构造器了。
    ctor 调出初始化器,名字要和类名一样,没有返回值的
    创建实例一定要new吗?
    不一定,
  1. 反射
    //反射
    

Type t=typeof(Student); //类是一种类型,看看student是什么类型 //并保存到类型类中,不能用类声明变量 object o=Activator.CreateInstance(t,1,"tom"); //Activator.CreateInstance,创建一个object类型的实例 //构造器直接就写在后面了。 Student stu= o as Student ; //需要找回 Student 类 ,才能调用其中的方法。 //as 是类的类型转换符号。 stu .Report();

2. dynamic编程
```csharp
//dynamic就硬找,
Type t=typeof(Student);
dynamic stu =Activator.CreateInstance(t,1,"tom");
stu.Report();
  • 析构器 析构器:用来主动释放内存:GC会自动释放内存,但有些底层的内存可能不能释放出去
    会在代码执行完毕后,再执行。

    Student stu=new Student(1,"tom");
    //声明了一个 stu 的变量,创建了Student的实例(new)
    stu.Report();
    class Student{
      public int ID  { get; set; }
      //ctor 调出初始化器,名字要和类名一样,没有返回值的
      public Student(int id,string name )
      {
          this.ID=id ;
          this.Name=name;
      }
      ~Student(){
          //析构器:用来主动释放内存:GC会自动释放内存,但有些底层的内存可能不能释放出去
          Console.WriteLine("记得关机哦");
          Console.ReadKey();
      }
      public string Name { get; set; }=string.Empty;
      public void Report(){
          Console.WriteLine($"my name is {Name},I am #{ID}");
          //$ 是对 string.Format({0},填入内容)的简写。
      }
    }
    
  • 静态
    为类赋予意义:如类的总的特征

 
 Console.WriteLine(Student .Amount);
//static 直接用类名就可以调用了
class Student{
public static  int Amount { get; set; }
   //加static 表示静态类,即student拥有的总的特点
   static Student()
   //静态构造器,初始化静态成员
   {
       Amount=100;
   }
}

类的声明

类的声明(class declarations):类是声明,类的实例是创建。

类的声明位置:

  1. 声明在名称空间(namespace)内
  2. 声明在全局名称空间中
  3. 声明在类体中(是类的成员(成员类))

声明与定义(在c++中是不一样的,声明定义是可以分开的)c#和Java是声明即定义。

类的主体

不能省略的部分 class,identifier标识符类的名字,类体class-body{}

类可以省的的部分

  1. 类的修饰符(class-modifiers) 在类的前面

按继承关系:

  • abstract(抽象的,必须继承)
  • sealed(密封的,禁止继承)

按访问级别:

  • public(公共的)
  • internal(内部的)

成员类的访问级别:

  • public(公共的)
  • internal(内部的)
  • protected(受保护)
  • private(私有的)
  • static(静态的)

new 修饰符适合于嵌套类。它指定类隐藏同名的继承成员,不在嵌套中使用会报错。

成员类的访问级别

如何创建类库,并且引用。

dotnet new classlib -o StringLibrary //创建数据库
//方法一:
dotnet sln add ShowCase/ShowCase.csproj//就可以引用数据库了
//方法二:
//在csproj文件中输入下面代码 ItemGroup(项目组)ProjectReference(项目引用) Include(包括)
  <ItemGroup>
 <ProjectReference Include="..\StringLibrary\StringLibrary.csproj" />
 </ItemGroup>
  1. public 与 internal

    
    

namespace MyLib.MyNamespace { internal class Calculator //public修饰符可以使得reference(参考的)关联的项目可以访问这个类。 //internal(内部的)只有在同一程序集(Assembly)的文件中,内部类型或成员才可访问. //可以访问的内部成员,要以internal修饰才可以直接访问。如果class没有修饰符默认是internal。 //以Mylib类库为例只有用internal修饰的程序才能访问。 //如果是以public为修饰符的内部成员class,可以实例化类,但不能访问内部成员 { public double Add (double a,double b){ return a +b ; } } }


##### 类的派生与继承

类继承语法 class 类名 : 基类名

所以的类都是由`object`派生而来的。

* 是一个概念 is a :一个子类的实例,语义是来讲也是一个父类的实例(一个派生类的实例,也是一个基类的实例)。例如 子类是老师 ,父类是人 ,老师是人。  
反过来不成立:即人不一定是老师。

* 可以用 父类的变量来引用 子类的实例。

* sealed(密封的,禁止继承)修饰符来修饰 class,无法派生新的类

* c#中一个类只允许有 一个基类,即一个子类只有一个父类。

* 子类的访问级别不能超过 父类。例如: 父类用 internal修饰,不能用public修饰子类。

1. 继承的特点

类继承的本质:派生类在基类已有的成员的基础上对基类进行横向或纵向上的扩展。

* 基类有什么成员,派生类就一个不拉全部接受,即子类全盘继承父类。
* 继承是扩展,类只能越来越多,不能削减。
* 横向扩展:类成员的数量越来越多,纵向扩展:不增加个数,对类的版本更新。

>* 子类全盘继承父类
这个在前面有,一个类默认带有object基类的方法,子类可以使用父类所有成员,但不能直接修改父类。

```csharp
//子类全盘继承父类
Car car =new Car ();
Console.WriteLine(car.Owner);
class Vehicle
{
 public string Owner { get; set; }
 public  Vehicle(){
     this.Owner="N/A";
 }
}
class Car:Vehicle//car类继承了vehicle(交通工具)类
{
 
}
  • 什么是基类对象,基类对象如何构造 基类构造器先运行,然后在运行派生类。
//构造器可以为基类赋值,
Car car =new Car ();
Console.WriteLine(car.Owner);
class Vehicle
{
    public string Owner { get; set; }
    public  Vehicle(){
        this.Owner="N/A";
    }
}

class Car:Vehicle//car类继承了vehicle(交通工具)类
{
    public Car(){
        this .Owner="car owner";
    }
}

base.关键字可以向上访问上一级对象,可以从派生类中调用基类的方法。

父类的实例构造器,不能被子类继承。构造器无法被继承

  1. 访问级别
  • 类成员的访问级别以类为上线,如:class A 用internal修饰,类成员最高也是internal 既是是用 public修饰的方法,也是不能被引用的。

  • 类成员为internal修饰,把类成员的访问级别限制在了同一个程序集中。

  • 类成员为private修饰,把类成员限制在类体中,子类都不能访问,但还是继承下来了。

  • 类成员不加修饰符为:private,实例私有字段 命名 用_+名字。

  • protect 把类成员限制在 继承链上,只有继承链上的成员才能访问。是可以跨程序集的。 通常用在方法上,internal(内部的)与protect合在一起是或的意思,既可以被程序集访问,又可以被子类访问。

面向对象的实现风格

  • class-based 基于类。
  • prototype(原型)-based java script用
重写,多态。

类成员的“纵向”发展

  • 通过为父类指定virtual(虚拟的) 方法,再通过子类 override(重写;覆写)方法完成重新。这时通过base.可以完成对子类上一级方法的调用。

重新 是对旧版本的更新,父类中的旧版本还有。子类有新版本的方法。

具体使用什么版本的类,根据是谁的实例决定的.

子类带有 override方法,子类的子类也带有 override方法,那么子类默认带有virtual 子类的子类可以为子类重新。

重写与隐藏的条件:函数成员(多为方法,属性),可见(不用private修饰符),签名一致(方法名字,参数类别要一致)

  • 多态(polymorphism) 基于重写机制(virtual>override) 用父类调用子类的实例,可以调用子类在继承链上最新的版本。(函数成员的具体行为(版本)由对象决定)
Vehicle v =new car ();
v .Run ();
Console.WriteLine(v.Speed);
class Vehicle
{
    private  int  _speed;
    public virtual int Speed { 
        get{return _speed; } 
        set{_speed=value;}
         }
    public virtual void Run (){
        Console.WriteLine("I'm running");
        _speed=100;
    }
}
class car :Vehicle
{
    public override void Run()
    {
        Console.WriteLine("Car is running");
        _rpm=5000;
    }
    private int _rpm;//发动机转速
    public override int Speed 
    { get {return _rpm/100;}
      set {_rpm=value *100;}
      }
}
class rasecar :car 
{
    public override void Run()
    {
        Console.WriteLine("rasecar is running");
        
    }
}
  • 如果没有写 virtual,override,直接以同名字来隐藏(hide)父类方法。 也重新,但是 在子类中会有两个同名字的方法,父类被隐藏了,谁来New就调用那个。c#这种重新几乎不用。Java里面默认是

接口,抽象类,SOLID ,单元测试,反射

接口,抽象类

什么是接口和抽象类

  • 接口和抽象类都是“软件工程产物”
  • 具体类>抽象类>接口,内部实现的东西越来越少,越来越抽象
  • 抽象类是未完全实现逻辑的类,接口完全未实现的类
  • 抽象类中可以有字段和非public成员,它们可以有具体的逻辑
  • 抽象类为复用而生:专门作为基类来使用,具有解耦功能
  • 封装确定的,开放不确定的,推迟到合适子类中实现。
  • 接口只要函数成员,成员全部默认public
  • 接口为解耦而生,“高内聚,低耦合”,方便测试
  • 接口是一个“协议”,
  • 它们都不能实例化,只能用来声明变量,引用具体类(concrete class)实例。

抽象类

抽象类为基而生,是做基的好方法,如:基类只要写一个方法的模板,子类去实现逻辑。
abstract(抽象的,必须继承)修饰符修饰的:即类中有一个或多个没有被完全实现的成员。没有被现实的成员要用abstract修饰,且不能用private修饰,不能有逻辑实现({}不能用)。
因为抽象成员没有具体的行为,所以不允许实例化抽象类。
抽象类作用:1,作为基类,派生出去,派生类实现没有实现的方法。2,作为基类,可以声明变量,调用子类实例(多态)。简称纯虚方法。
子类要用override修饰,本质还是一种重新,更加简洁。

开闭原则:不是添加方法,修改bug,不要修改类的代码(特别是函数成员的代码)。即应该封装哪些不变的,稳定的,确定的成员,把不确定,有可能改变的成员,声明为抽象成员,给子类实现。

//为做基类而生的“抽象类”与“开放/关闭原则”
Vehicle v=new Car();
v.Run();
abstract  class Vehicle
{
    public void Stop(){
        Console.WriteLine("stopped!");
    
    }
    public abstract void Run(); 
    //有几个抽象类,子类就要补全几个抽象类不能少补。
    
}

class Car:Vehicle
{
    public override void Run()
    {
        Console .WriteLine("car is running");
    }
    
}
class Bus:Vehicle
{
    public override void Run()
    {
        Console .WriteLine("bus is running");
    }
}

接口

接口成员一定是public的,接口的本质:服务调用者,服务提供者之间的契约。

在代码中有可以替换的地方就有接口的存在。接口为了解耦而生。

var user=new PhoneUser(new  NokiaPhone());
//通过接口调用,不同的接口子类。从而降低耦合性。
//接口里面是子类共同有的方法。

interface IPhone
{
    void Call();
    void PickUp();
    void Send();
    void Receive();
}
class PhoneUser
{
    private IPhone _phone;
   //使用者存储接口

    public PhoneUser(IPhone phone )
     //使用者接收 接口
    {
        _phone=phone;
    }
    public void UsePhone(){
        //调用接口方法。
        _phone.Call();
        _phone.PickUp();
        _phone.Receive();
        _phone.Send();
    }
}
class NokiaPhone : IPhone{
    public void Call()
    {
       Console.WriteLine("你好,我是某某");
    }

    public void PickUp()
    {
        Console.WriteLine("你是某某吗");
    }

    public void Receive()
    {
        Console.WriteLine("恭喜你获得一等奖");
    }

    public void Send()
    {
        Console.WriteLine("骗子");
    }
}

class MiPhone : IPhone
{
    public void Call()
    {
        Console.WriteLine("你好,我是某某");
    }

    public void PickUp()
    {
         Console.WriteLine("你是某某吗");
    }

    public void Receive()
    {
        Console.WriteLine("恭喜你获得一等奖");
    }

    public void Send()
    {
        Console.WriteLine("骗子");
    }
}


反射

反射与依赖注入

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

额。。。。。先学玩设计模式在看

泛型,partial类,枚举,结构

泛型

泛型(generic)

  • 泛型:泛指的类型。解决类型膨胀的问题。

    //泛型一个属性
    var apple=new Apple(){Color="Red"};
    var book=new Book(){Name="New Book"};
    
    Box<Apple>box1=new Box<Apple>(){Cargo =apple };
    //使用泛型必须进行特化,<传入的类型用了替换泛型>=
    //new 一个新的类型(){泛型的属性=想要特化的类的属性}
    Box<Book>box2= new Box<Book>(){Cargo=book };
    Console.WriteLine(box1.Cargo.Color);
    
    class Apple
    {
      public string Color { get; set; }
    }
    class Book
    {
      public string Name { get; set; }
    }
    class Box<TCargo>
    //类型参数,一个标识符,代表一个泛化的类型(type)
    {
      public  TCargo Cargo {get ;set ;}
      //声明了一个泛化的属性,不能直接使用。
      
    } 
    
    
//泛型一个接口。
student<int >stu =new student<int>();
//使用泛型必须进行特化,<传入的类型用了替换泛型>=
//泛型类,可以改变数据类型,<>中可以选择想要的数据类型
stu .ID=101;
stu .Name="Tom";

var cat=new Cat();
cat.ID=101;
cat.Name="Kitty";
interface IUnique<TId>
{
    TId ID { get; set; }
}

class student<TId> : IUnique<TId>
//实现泛型类接口的类,本身也是泛型的
{
    public TId ID { get ; set ; }
    //TId 泛型只影响 ID 属性,泛型接口实现了,就不影响类的其他成员。
    public string Name { get; set; }
}

class Cat : IUnique<ulong>
//可以在声明泛型类的时候就确定 类的类型。直接特化类
//可以为自定义类声明数据类型
{
    public ulong ID { get; set; }
    public string Name { get; set; }
    
}

泛型: using System.Collections.Generic;里面有泛型类的接口
IList<int>list=new list<int>();可以同过IList接口新建一个list实例。list数组的长度可以改变,又叫动态数组。
带有ICollection接口代表是一个集合,IEnumerable接口代表可以被迭代

泛型类<可以有两个泛型> IDictionary<int,string>dic=new Dictionary<int,string>[]

算法与泛型: 泛型数据类型,可以代替算法的数据类型,可以更加灵活使用算法。

泛型委托。
action 委托引用没有返回值的方法 Func<>(function)委托引用有返回值的方法,<需要参数,需要参数,返回值> Lambert表达式,对于逻辑非常简单的方法,在调用的地方谁调用谁申明,并且是匿名声明。

partial 类

把一个类的代码分成一部分或多部分编写,每个部分根据自己速度进行版本更新。

  • 减少类的派生
  • partial类与entity framework
  • partial类与Windows forms,wpf ,ASP.NET.core 要用到类库的知识等等

枚举类型

  • 人为限定取值范围的整数
person person =new person();
person.level=Level.BigBoss;
person.skill=Skill.Cook|Skill.Drive|Skill.Program|Skill.Teach;
Console.WriteLine((person.skill&Skill.Cook)==Skill.Cook);

enum Level
{
    Employee,//默认0,可以改
    //如果赋值 为100,下面没有赋值,默认+1.
    Manager,//1
    BOSS,//2
    BigBoss,//3
}
//比特位用法
//十进制是二的次方的数,用|符号(位或 二进制按位求或,有真则真。)将枚举向累加,获得多个枚举结果。
enum Skill
{
    Drive=1,//二进制 1
    Cook=2,//二进制 10
    Teach=4,//二进制100
    Program=8,//二进制1000
}

class person
{
    public int ID { get; set; }
    public string Name { get; set; }    
    //枚举类型,约束输入类型
    public Level level {get;set;}
    //比特位用法
    public Skill skill  { get; set; }
}

结构体类型


本文章使用limfx的vscode插件快速发布