显示控制台
1.点击vs code 中的launch(开始{应用程序})
2.修改 console:“internal Console(内部的)”,改为externalTermianl(外部,终端)
"console": "externalTerminal",
最后输入代码 console.Readkey(); 控制什么时候结束窗口.
打开 cmd 控制台 输入dotnet new -l
获得dotnet可以创建的类型
控制台应用 console [C#],F#,VB Common/Console
什么是操作符:
- 操作符(operator)也叫“运算符”
- 操作符是用来操作数据的,被操作的数据也叫操作数。
- 操作符的运算中 同个级别 操作符都是从左向右--其中赋值操作符-->从右向左。
操作符在下表格中类型排名越前优先级越高。
为什么要使用操作符?
- 如果没有+,只有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);
如何创建一个自己的操作符方法?
操作符优先级通过 () 提高优先级
System.IO.File.Create("D\\hello,world.txt");
//访问了system命名空间的子集IO 调用了IO中file(静态成员)
//并调用了create方法创建了一个文本
//符合1,2,3
dotnet add package System.Windows.Forms//在vscode中使用form要中终端引用。
需要new一个实例
。f(x) f值函数(function)
x 方法调用的参数
Calculator c=new Calculator ();
int x=c .Add(10,20); //方法 (参数)
在委托(间接调用)类型中 f(x)中被调用的方法不用(),
Action myAction=new Action (c.Print);//只方法名字就可以委托了
myAction ();
a[x]中的[x]表示的是索引
int []myIntArray=new int []{1,2,3};
//int 表示数组的默认类型 []表示数组的长度 {}数组初始化器。
//[]可以不写 写了要和后面初始化器里面的长度对应。
Console.WriteLine(myIntArray[0]);
//这的[]表示访问数组元素 从0开始,代表第一位 表示集合的索引。
Console.WriteLine(myIntArray.Length-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;
}
遇到赋值运算符:先赋值,再运算。
int a =100;
int b =a ++ ;//这时b=100,a=101
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
}
new 操作符在内存中创造一个实例,并调用实例构造器(instance constructor)。
from myForm =new from ();
//右边 new from创建实例 ()调用实例构造器
//左边 new操作符拿到的内存地址 ,交给要访问的引用变量myForm。
调用实例的初始化器,可以初始化多个。
是加上{}:from myForm =new from (){Text=“Hello,...,...”};
直接访问方法,不创建实例
new form(){}.ShowDialog();
因为没有引用变量牵住它,所以是一次性的,会被回收。无法再次调用
语法糖衣:例如一.string是引用类型,但不用new ,内置语法糖衣
例如二,int[]myArray=new int[10];
可以写成int[]myArryay={1,2,3}
;
匿名类型 为匿名类型创建对象并且用隐式类型变量来引用这个实例。
var cat=new {Name="汤姆",Age=8};
//new 实例后面跟一个对象初始化器,不知道具体是什么数据类型,就用var引用这个实例
Console .WriteLine(cat.Name);
Console.WriteLine(cat .GetType().Name);
依赖注入设计模式:可以将紧耦合 改为相对松的耦合方式。
new public void a(){}
作为修饰符 可以隐藏父类的方法。少见checked检查一个值是否在内存中有溢出。 使用方法一 用做操作符
uint x =uint .MaxValue;
uint y=checked(x+1);//结果是二进制每一位加1 结果为0
//c#默认是unchecked;
使用方法二 用做语句块
checked
{
uint x =uint .MaxValue;
uint y=x+1;
}
delegate(委派 ... 为代表)也是关键字,操作符使用淘汰了,通常用来作委托用。 现在用lambda表达式使用。
sizeof 对象在内存中所占字节数。
注意事项:1 只能获得基本数据类型(有关键字的,是结构体类型的)所以排除string,object。
2 在非默认的情况下 可以获得自定义结构体的实例 要放在不安全的上下中运行。
需要配置不安全环境 unsafe
类似与c,c++指针,用来取地址,只能用来操作结构体类型,还要在不安全的环境下运行。
一元操作符也叫单目操作符,只有一个操作数。
不常用,也要不安全的情况下使用。
正+,负-,非!,反~
+,-与数学不一致的地方:数据类型是值是会溢出的,最小的绝对值和最大的绝对值是不对称的。
~,取反操作符:在一个数的二进制是按位取反,与相反数不同,相反数是按位取反还要加1
!取非操作符:只能用来操作布尔类型的值。真变假,假变真。
先运算++,--,后进行赋值运算。
T(X)强制类型转换操作符。
类型转换的方式:
隐式(implicit)转换类型 //编译器自动转换。 不丢精度(字节小的转字节大的):
父类可以隐式转换子类 ,但是不能调用子类的方法
在C#中引用变量只能访问 变量类型的方法,成员,不能访问变量引用实例类型的方法
Teacher t=new Teacher();
Human h=t;//可以完成隐式转换,但不能调用子类方法。
class Animal{}
class Human:Animal{}//指定基类(父类),子类会继承基类里面的成员。
class Teacher:Human{}
装箱(把一个值类型从栈搬到堆里面)
显示(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;
}
/ * % + -
与数学基本一致
注意事项
<< >>
数据在二进制结构向左,向右平移.
没有溢出的情况下 左移 就是*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合并 可空类型(并不是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#中 表达式是算法的最基本(最小)单元,表达一定的算法意图。
要有一个||多个操作数 && 没有||多个操作符组成的序列。
Action myAction-new Action(console.WriteLine);
console.WriteLine就是由类名和方法构成的成员访问表达式,结果是一个方法。System.Windows.Forms.Form myform=new form();
System.Windows就是主名称空间.子名称空间。表达式的优先级,根据操作符的优先级决定。
var t=typeof(int32);
Console.Writeline();
拿到的是一组方法根据数据类型选择调用的方法(重载决策)。语句定义:广义最小的独立元素,功能表达一些将被执行的动作。等价于一个或一组有明显逻辑关联的指令。语句由表达式组成。
语句是高级语言中独有的(高级语言中的表达式对应低级语言中的指令)
c#中定义
让程序员顺序地(sequentiall)
表达算法思想。通过条件判断和循环等方法控制程序的逻辑走向。
功能:陈述算法思想,控制逻辑走向,完成有意义的动作
action
c#中语句以;结尾,但以;结尾不一定是语句using System指令
|||public string Name;字段的声明
语句一定是在方法体里面的。
语句分类:labeled-statement标签语句(用的不多),declaration-statement声明语句,embedded-statement嵌入式语句
int x=10,y,z=3;//这里的=号是本地变量初始器,不叫赋值。这么写会减低可读性。
int[]myArray={1,2,3}//数组初始化器是{}。
y=100;//这里是赋值的含义。
var a="你好";
const double pi=Math .PI;
这些表达式如果有值,没有用变量接受值,值会被丢弃。
遇到只是想要表达式执行的动作时候可以使用。
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 异步编程用。
块语句是一个语句容器,容纳多条语句,但是算一条语句。
格式:{语句列表}
//可以空。什么语句都可以写在块语句里面。
{}出现在方法体里面才是块语句。不用加;
。
变量的作用域:块语句外
不能访问块语句内变量;但块内部可以访问外部变量。
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 标签有 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分为两类:一是通用
,二是捕捉专门的错误:
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 ;
}
有四种不同的循环语句,while,do,for,foreach语句
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("游戏结束" );
}
}
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);
Console.WriteLine("输入一个数字");
try
{
string str1=Console .ReadLine();
x =int.Parse(str1);
}
catch
{
Console .WriteLine("请正确的的数字");
continue;//如果输入的数字错误,会 重新开始循环,所以要写在循环体中。
}
break语句 :立刻结束循环,执行循环外部的代码。 string.Tolower();转为小写。
如果是多重迭代(循环)continue和break语句执行的是包裹住他们的循环语句。
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();
}
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 作用对集合里面的元素进行遍历。
表达数据。
实例字段
,属于某个对象,表示对象当前的状态静态字段
,属于某个数据类型,表示某个数据类型当前的状态。有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 ;
}
}
}
用来检索一个集合。
声明的时候用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);
}
}
}
}
常量:表示常量值,在编译的时候替换常量符号如pi;
存储不变的值,可以提高运行效率。
常量只能是一个基本数据类型
实例常量是只读实例字段,本质还是变量
局部常量是在Main 类中
声明成员常量
class student
{
public const string WebMocc="https://www.icourse163.org/";
}
传值参数也叫值参数(形参):声明的时候不带任何修饰符的参数。
实参的副本,永远不会影响实参的值。(像是用实参的值,创建了一个新的变量)
为当前方法创建一个局部变量,(看成实参的副本,改变值参数,不会影响实参)
初始值:在调用方法时,赋予的值(实参)
在方法中改变了传值参数,不会影响变量的值(方法调用时候赋予的值)
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==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 );
}
什么是扩展方法
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);
委托(delegate):是函数指针的“升级版”
通过函数名字调用函数:为直接调用。 指针,间接调用.效果和直接调用一样。
int x=100;int y=200;
typedef int(*Calc)(int a,int b);//定义指针类型
Calc fucPoint1=&Add;//声明指针变量
z=fucPoint1(x,y);//调用方法
地址
直接调用与间接调用的本质
//函数指针的升级版,按照一定约束指向目标方法,完成方法的间接调用。
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)
//用委托类型的参数封装一个外部的方法,把方法传入内部进行间接调用。
//通过实例 开始生产产品
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;
}
}
委托的注意事项
最后是用接口(interface)代替委托,提高代码的可读性。
事件(event):能够发生的什么事情。
在c#中,是类型的成员,
事件参数(eventArgs):事件发出的消息,
只有事件,没有事件参数的事件:无需额外消息,事件本身就是消息了。
处理事件:根据事件,采取行动,由event Handler事件处理器,来处理事件。
功能:使对象或类具有通知(notification)能力的成员。
事件功能=通知+可选的事件参数(详细信息)
用法:用于对象或类之间的动作协调与信息传递(消息推送);
某对象拥有什么事件:指的是对象可以通过事件发信息给关心这个事件的别的对象,别的对象得到信息,做出响应。程序运转起来了。
事件模型/发生响应模型(event model):事件的固定模式
事件模型组成:发生>响应的5个部分:孩子 饿了 我 做法,其中暗含着:孩子要和我有关系我才给孩子做饭(被叫做“订阅”关系),使用是5部分。
事件模型构建运作:发生>响应5个步骤:1.我要有一个事件,2.一个或一群人关心这个事件,3.我这个事件发生了,4.关心这个事件的人会被依次通知到,通知的次序是订阅事件的次序。5,关心事件的人拿到事件信息(事件数据,事件参数,通知
)做出响应(处理事件
)。
事件术语约定
事件主要是用于开发桌面,手机客户端
,与用户打交道。用户操作数据,得到新结果,完成一次事件。
叫做事件驱动的程序。
设计模式:MVC,MVP,MVVM,事件模式。
新版本的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,会显示文字
}
}
}
事件是基于委托的:
事件需要委托类型来约束:约束事件能发什么样的消息给事件响应者,也规定了事件响应者能收到什么样的事件消息。
所以事件处理器 要与这个约束匹配是,才能订阅事件。
事件处理器可以匹配事件后,需要保存记录事件处理器:需要委托类型的实例。
以下面客人点单的案例为例: 事件的拥有者是:客人 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 ;
//看上去 少了一个委托类型的存储,
委托类型可能会在类的外部滥用,所以用事件来限制委托。 事件本质是委托字段的包装器,(像是字段和属性的关系),限制委托字段的访问,
声明事件的委托类型的命名约定
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,演化历史悠久)
触发A事件的命名方法(触发一定要是事件的拥有者去做),OnA,即因何引发
,事出有因
。
事件的命名约定
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.
为什么使用委托类型来声明事件?
对比事件与属性
都是包装器,用于保护包装的东西,不是东西本身。
什么是“类”:
构造器与析构器
//反射
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):类是声明,类的实例是创建。
类的声明位置:
声明与定义(在c++中是不一样的,声明定义是可以分开的)c#和Java是声明即定义。
不能省略的部分 class
,identifier标识符
类的名字,类体class-body{}
按继承关系:
按访问级别:
成员类的访问级别:
new 修饰符适合于嵌套类。它指定类隐藏同名的继承成员,不在嵌套中使用会报错。
如何创建类库,并且引用。
dotnet new classlib -o StringLibrary //创建数据库
//方法一:
dotnet sln add ShowCase/ShowCase.csproj//就可以引用数据库了
//方法二:
//在csproj文件中输入下面代码 ItemGroup(项目组)ProjectReference(项目引用) Include(包括)
<ItemGroup>
<ProjectReference Include="..\StringLibrary\StringLibrary.csproj" />
</ItemGroup>
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.
关键字可以向上访问上一级对象,可以从派生类中调用基类的方法。
父类的实例构造器,不能被子类继承。构造器无法被继承
类成员的访问级别以类为上线,如:class A 用internal修饰,类成员最高也是internal 既是是用 public修饰的方法,也是不能被引用的。
类成员为internal修饰,把类成员的访问级别限制在了同一个程序集中。
类成员为private修饰,把类成员限制在类体中,子类都不能访问,但还是继承下来了。
类成员不加修饰符为:private,实例私有字段 命名 用_+名字。
protect 把类成员限制在 继承链上,只有继承链上的成员才能访问。是可以跨程序集的。 通常用在方法上,internal(内部的)与protect合在一起是或的意思,既可以被程序集访问,又可以被子类访问。
面向对象的实现风格
类成员的“纵向”发展
virtual(虚拟的)
方法,再通过子类 override(重写;覆写)
方法完成重新。这时通过base.
可以完成对子类上一级方法的调用。重新 是对旧版本的更新,父类中的旧版本还有。子类有新版本的方法。
具体使用什么版本的类,根据是谁的实例
决定的.
子类带有 override方法,子类的子类也带有 override方法,那么子类默认带有virtual 子类的子类可以为子类重新。
重写与隐藏的条件
:函数成员(多为方法,属性),可见(不用private修饰符),签名一致(方法名字,参数类别要一致)
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");
}
}
同名字
来隐藏(hide)父类方法。 也重新,但是 在子类中会有两个同名字的方法,父类被隐藏了,谁来New就调用那个。c#这种重新几乎不用。Java里面默认是什么是接口和抽象类
抽象类为基而生,是做基的好方法,如:基类只要写一个方法的模板,子类去实现逻辑。
被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("骗子");
}
}
反射与依赖注入
额。。。。。先学玩设计模式在看
泛型(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表达式,对于逻辑非常简单的方法,在调用的地方谁调用谁申明,并且是匿名声明。
把一个类的代码分成一部分或多部分编写,每个部分根据自己速度进行版本更新。
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插件快速发布