/*
*文章未优化完成,我会尽快维护
*/
目錄
一、类库:图书馆,命名空间:书架,类:书籍,方法:目录
1、类库的引用:
2、初识类与名称空间:
3、依赖关系:
4、类与对象的关系:
5、类的三大成员:
6、静态成员与实例成员:
二、C#语言的基本元素、类型、变量、方法、算法:
1、构成C#语言的基本元素:
2、简要介绍类型、变量和方法:
3、算法简介:
三、类型
1、什么是类型(Type)
2、类型在C#语言中的作用
3、C#语言的类型系统
四、变量、对象与内存
1、什么是变量:
2、值类型的变量
3、引用类型的变量和实例
4、局部变量是在stack(栈)上分配内存
5、变量的默认值
6、常量(值不可改变的变量)
7、装箱与拆箱(Boxing & Unboxing)
五、方法的定义、调用与调试:
1、方法的由来:
2、方法的定义与调用:
3、构造器(一种特殊的方法):
4、方法的重载:
5、如何对方法进行Debug
6、方法的调用与栈(*难点)
六、操作符
1、操作符概述
2、操作符的本质
3、操作符的优先级
4、同级操作符的运算顺序
5、基本操作符
6.一元操作符
七、表达式和语句
1、表达式的定义
2、各式表达式概览
3、语句的定义
4、语句详解
八、字段、属性、索引器、常量
1、字段
2、属性:
3、索引器
4、常量
九、传值/输出/引用/数组/具名/可选参数,扩展方法
1、传值参数
2、输出参数
3、引用参数
4、数组参数
5、具名参数
6、可选参数
7、扩展方法(this参数)
十、委托
1、什么是委托
2、委托的声明(自定义委托)
3、委托的使用
十一、事件
1、初步了解事件
2、事件的应用
3、深入理解事件
4、事件的声明
5、问题辨析
一、类库:图书馆,命名空间:书架,类:书籍,方法:目录
1、类库的引用:
(1) 类库引用是使用名称空间的物理基础
不同技术类型的项目会默认引用不同类型的类库。
(2) DLL引用(黑盒引用,无源代码)
NuGet简介(DLL服务器)
(3) 项目引用(白盒引用,有源代码)
2、初识类与名称空间:
(1) 类(class)是构成程序的主体【初学者需要知道的定义】。
(2) 类(class)是最基础的C#类型。类是一个数据结构,将状态(字段)和操作(方法和其他函数成员)组合在一个单元中。类为动态创建的类实例(instance)提供了定义,实例也称为对象(object)。类支持继承(inheritance)和多态性(polymorphism),这是派生类(derived class)可用来扩展和专用化基类(base class)的机制【C#语言文档的定义】。
(3) 类(class)是现实世界事物的模型;
类是对现实世界事物进行抽象所得到的结果;
[1]事物包括“物质”(实体)与“运动”(逻辑);
[2]建模是一个去伪存真、由表及里的过程(将程序中用到的东西留下;“表” 就是看得到的接口,“里”就是看不到的封装的逻辑。)。
名称空间(namespace):以树型结构组织类(和其他类型),例如:Button和Path类。
命名空间保证同名的类不冲突。
3、依赖关系:
(1) 类(或对象)之间的耦合关系;
(2) 优秀的程序追求“高内聚,低耦合”;
高内聚:将类放到该放的类库中;
低耦合:类与类之间的关系尽量低依赖;
教学程序往往会违反这个原则;
(3) UML(通用建模语言)类图;
【*依赖关系是软件质量的关键。】
4、类与对象的关系:
(1) 对象也叫实例,是类经过“实例化”后得到的内存中的实体;
Formally “instanc” is synonymous with “object”——对象和实例是一回事;
“飞机”与“一架飞机”有何区别?天上有(一架)飞机——必须是实例飞,概念是不能飞的;
有些类是不能实例化的,比如“数学”(Math Class),我们不能说“一个数学”。
(2) 依照类,我们可以创建对象,这就是“实例化”;
现实世界中常称“对象”,程序世界中常称“实例”,二者并无太大区别,常常混用。
(3) 实例化:使用new操作符创建类的实例,后面加上()构造器。
(4) 引用变量与实例的关系;
孩子与气球;
气球不一定有孩子牵着;
多个孩子可以使用各自的绳子牵着一个气球,也可以都通过一个绳子牵着气球。
namespace ClassAndInstance
{
class Program
{
static void Main(string[] args)
{
Form myForm; //创建引用变量
myForm = new Form(); //将new出来的实例Form,赋值给引用变量myForm
myForm.Text = "MyForm"; //给Form的标题命名为MyForm
myForm.ShowDialog(); //显示输出窗口
}
}
}
5、类的三大成员:
(1) 属性(Property)
存储数据,组合起来表示类或对象当前的状态
(2) 方法(Method)
由C语言中的函数(function)进化而来,表示类或对象“能做什么”
工作中90%的时间是在与方法打交道,因为它是“真正做事”、“构成逻辑”的成员
namespace EventSample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//*实例化
DispatcherTimer timer = new DispatcherTimer();
//*给属性值
//间隔多长时间触发一次Tick(这里是一秒钟)
timer.Interval = TimeSpan.FromSeconds(1);
//*挂接事件处理器
//事件“Tick”通过“+=”将函数“timer_Tick”挂接,这个事件触发时,下面的函数就会执行。
//所以这个方法叫做事件处理器。
timer.Tick += timer_Tick;
//*方法调用
//让时钟开始
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
//事件触发时,执行下面方法
//访问当前时间(时钟)
this.timeTextBox.Text = DateTime.Now.ToString();
}
}
}
(3) 事件(Event)
类或对象通知其它类或对象的机制,为C#所特有(Java通过其他办法实现这个机制)
善用事件机制非常重要
(4) 使用MSDN文档
(5) 某些特殊类或对象在成员方面侧重点不同
模型类或对象重在属性,如:Entity Framework
工具类或对象重在方法,如:Math,Console
通知类或对象重在事件,如各种Timer
6、静态成员与实例成员:
(1) 静态(Static)成员在语义上表示它是“类的成员”
(2) 实例(非静态)成员在语义表示它是“对象的成员”
namespace StaticSample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello!"); //*静态:WriteLine隶属于Console,由Console直接调用
Form form = new Form(); //*实例:实例化,定义变量form
form.Text = "Hello!"; //通过变量form来调用实例的方法“Text”
form.ShowDialog();
}
}
}
(3) 绑定(Binding)指的是编译器如何把一个成员与类或对象关联起来
不可小觑的“.”操作符——成员访问
(类.类成员,实例.实例成员)
二、C#语言的基本元素、类型、变量、方法、算法:
1、构成C#语言的基本元素:
(1)关键字(Keyword)
MSDN文档:Visual C#->C#Reference(所有关键字)
(2)操作符(Operator)
Visual C#->C#Reference->C#Operators(所有运算符)
(3)标识符(Identifier)
合法的标识符(阅读语言定义文档)
大小写规范(变量名用驼峰命名法,类、方法用Pascal命名法[所有单词首字母大写])
命名规范
int inForm = 100; //驼峰命名法
Console.WriteLine(inForm); //Pascal命名法
(4)标点符号
(5)文本(字面值)
整数(多种后缀)
int z = 10;
long h = 10L;
实数(多种后缀)
float X = 10.0F;
double Y = 20.0D;
字符
char c = ‘a’;
字符串
string str = “abcde”;
布尔
bool b = true;
bool b2 = false;
空(NULL)
(6)注释与空白
单行
多行(块注释)
2、简要介绍类型、变量和方法:
(1)初识类型(Type)
亦称数据类型(Data Type)
var v = 3L;
Console.WriteLine(v.GetType().Name); //INT64
(2)变量是存放数据的地方,简称“数据”
变量的声明
变量的使用
(3)方法(旧称函数)是处理数据的逻辑,又称“算法”
方法的声明
方法的调用
namespace Identifiers
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
int x = c.Add(2,3);
Console.WriteLine(x);
Calculator d = new Calculator();
string str = d.Today();
Console.WriteLine(str);
Calculator a = new Calculator();
a.PrintSum(4,5);
}
}
class Calculator
{
public int Add(int a, int b)
{
int result = a + b;
return result;
}
public string Today()
{
int day = DateTime.Now.Day;
return day.ToString();
}
public void PrintSum(int a, int b)
{
int result = a + b;
Console.WriteLine(result);
}
}
}
(4)程序=数据+算法
3、算法简介:
(1)循环体验
(2)判断体验
namespace Identifiers
{
class Program
{
static void Main(string[] args)
{
Calculator x = new Calculator();
x.PrintXTo1(10);
}
}
class Calculator
{
//public void PrintXTo1(int x) //循环输出
//{
// for (int i = x; i > 0; i--)
// {
// Console.WriteLine(i);
// }
//}
public void PrintXTo1(int x) //判断输出(自己调用自己)
{
if (x == 1)
{
Console.WriteLine(x);
}
else
{
Console.WriteLine(x);
PrintXTo1(x-1);
}
}
}
}
(3)1~100相加
namespace Identifiers
{
class Program
{
static void Main(string[] args)
{
Calculator x = new Calculator();
int result = x.SumFrom1ToX(100);
Console.WriteLine(result);
}
}
class Calculator
{
//public int SumFrom1ToX(int x) //循环
//{
// int result = 0;
// for (int i = 1; i < x + 1; i++)
// {
// result = result + i;
// }
// return result;
//}
//public int SumFrom1ToX(int x) //递归
//{
// if (x==1)
// {
// return 1;
// }
// else
// {
// int result = x + SumFrom1ToX(x-1);
// return result;
// }
//}
public int SumFrom1ToX(int x)
{
return (1 + x) * x / 2;
}
}
}
【*注:ctrl+k+c:所选部分注释; ctrl+k+u:所选部分解除注释。】
三、类型
1、什么是类型(Type)
(1)又名数据类型(Data Type)
A data type is a homogeneous collection of values,effectively presented,equipped presented,equipped with a set of operations which manipulate these values.
是数据在内存中存储时的“型号”。
小内存容纳大尺寸数据会丢失精准度、发生错误。
大内存容纳小尺寸数据会造成浪费。
编程语言的数据类型与数据的数据类型不完全相同。
namespace StrongTyped
{
class Program
{
static void Main(string[] args)
{
int x; //int:32个bit,4字节
x = 100;
long y; //long:64个bit位,8个字节
y = 100L;
x = 100L; //int型的x,放不了100L
bool b;
b = 100; //识别不了数值
if( x = 200 ) //因为x被赋值200,不是布尔值,就输出不了
{
Console.WriteLine("It's OK!");
}
}
}
}
(2)强类型语言与弱类型语言的比较
强类型语言:受数据类型影响较大,弱类型语言则相反。
C语言示例:if条件。
JavaScript示例:动态类型。
C#语言对弱类型/动态类型的模仿。
int _tmain(int argc, _TCHAR* argv[])
{
int x = 100;
if( x = 200 ) //在if语句中被重新赋值为200,会输出结果。因此常被写作 200==x
{
printf("It's OK!\n");
}
return 0;
}
2、类型在C#语言中的作用
(1)一个C#类型中所包含的信息有:
[1]存储此类型变量所需的内存空间大小。
[2]此类型的值可表示的最大最小值范围。
[3]此类型所包含的成员(如方法、属性、事件等)。
[4]此类型由何基类派生而来。
namespace TypeSample
{
class Program
{
static void Main(string[] args)
{
Type myType = typeof(Form);
PropertyInfo[] pInfo = myType.GetProperties(); //PropertyInfo:属性的信息
MethodInfo[] mInfo = myType.GetMethods(); //MethodInfo:方法的信息
//foreach(var p in pInfo)
//{
// Console.WriteLine(p.Name);
//}
foreach(var m in mInfo)
{
Console.WriteLine(m.Name);
}
Console.WriteLine(myType.Name);
Console.WriteLine(myType.FullName);
Console.WriteLine(myType.BaseType);
Console.WriteLine(myType.BaseType.BaseType.FullName);
}
}
}
[5]程序运行的时候,此类型的变量在分配在内存的什么位置:
Stack简介(栈),
Stack overflow,
Heap简介(堆),
使用Performance Monitor查看进程的堆内存使用量,
关于内存泄漏;
/*栈*/
namespace StackOverflow
{
class Program
{
static void Main(string[] args)
{
BadGuy bg = new BadGuy(); //先创建实例
bg.BadMethod(); //调用方法
}
}
class BadGuy
{
public void BadMethod()
{
int x = 100;
this.BadMethod(); //递归:不断的调用自己,最后栈会爆掉
}
}
}
namespace StackOverflow
{
class Program
{
static void Main(string[] args)
{
unsafe
{
int* p = stackalloc int[9999999]; //在栈上面切内存,只要足够大就会爆掉
}
}
}
}
/*堆*/
namespace HeapSample
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
List<Window> winList; //变量,声明在函数之外,Window占用内存高
private void Button1_Click(object sender, RoutedEventArgs e)
{
winList = new List<Window>(); //创建List实例,可以装Window
for (int i = 0; i < 15000; i++ )
{
Window w = new Window(); //引用变量w,牵着Windows()实例
winList.Add(w); //加入队列
}
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
winList.Clear(); //清除内存
}
}
}
[6]此类型所允许的操作(运算)。
namespace TypeSample
{
class Program
{
static void Main(string[] args)
{
double result = 3.0 / 4.0;
double results = 3 / 4;
Console.WriteLine(result); //0.75
Console.WriteLine(results); //0
}
}
}
3、C#语言的类型系统
(1)C#的五大数据类型
[1]类(class):如Window,Form,Console,String
[2]结构体(Structures):如Int32,Int64,Single,Double
[3]枚举(Enumerations):如HorizontalAlignment,Visibility
[4]接口(Interfaces):
[5]委托(Delegates):
//class:声明和定义 类
//struct:声明和定义 结构体
//enum:声明和定义 枚举类型
(2)C#类型的派生谱系
四、变量、对象与内存
1、什么是变量:
表面上来看(从C#代码的上下文行文来看),变量的用途是存储数据。
实际上,变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量。
变量一共有7种:
静态变量,实例变量(成员变量,字段),数组元素,值参数,引用参数,输出形参,局部变量。
namespace TypelnCSharp
{
class Program
{
static void Main(string[] args)
{
//1、Student.Amount:静态成员变量
Student stu = new Student();
//2、stu.Add:非静态成员变量
int[] array = new int[100];
//3、array[0]:数组元素,0~99
}
}
class Student
{
public static int Amount; //创建静态成员
public int Age; //创建非静态成员
public string Name;
public double Add(double a, double b) //double a和double b:是值参数变量;ref double a:引用参数变量;out double a:输出参数变量
{
double result = a + b; //result是方法Add的局部变量
return result;
}
}
}
狭义的变量指局部变量,因为其他种类的变量都有自己的约定名称:
简单地讲,局部变量就是方法体(函数体)里生命的变量。
变量的声明:
格式:有效的修饰符组合类型opt 类型 变量名 初始化器opt。
如:public static int Amount = 0;
2、值类型的变量
以byte/sbyte/short/ushort为例
static void Main(string[] args)
{
short s;
s = -1000;
string str = Convert.ToString(s,2);
Console.WriteLine(str); //1111110000011000
}
值类型没有实例,所谓的“实例”与变量合而为一
static void Main(string[] args)
{
short a = new short(); //和 short a; 一样
short a;
}
3、引用类型的变量和实例
引用类型变量与实例的关系:引用类型变量里存储的数据是对象的内存地址。
namespace TypelnCSharp
{
class Program
{
static void Main(string[] args)
{
Student stu; //引用类型,系统自动分配4个字节,存放实例堆内存地址
stu = new Student(); //创建实例,将Student实例的堆内存地址存储在变量stu中
Student stu2;
stu2 = stu; //stu2同样存储实例的堆内存地址,这就是两个孩子牵着同一个气球
}
}
class Student
{
uint ID; //4个字节,值内存
ushort Score; //2个字节,值内存
}
}
4、局部变量是在stack(栈)上分配内存
5、变量的默认值
namespace TypelnCSharp
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student();
Console.WriteLine(stu.ID); //0
Console.WriteLine(stu.Score); //0
}
}
class Student
{
public uint ID; //一但变量在内存中分配好后,内存块就会全部刷成0,默认值就变成0
public ushort Score; //添加public,就可以从外部看到
}
}
namespace TypelnCSharp
{
class Program
{
static void Main(string[] args)
{
int x; //本地变量必须要有赋值,否则就编译不过去
Console.WriteLine(x);
}
}
class Student
{
public uint ID; //一但变量在内存中分配好后,内存块就会全部刷成0,默认值就变成0
public ushort Score; //添加public,就可以从外部看到
}
}
6、常量(值不可改变的变量)
namespace TypelnCSharp
{
class Program
{
static void Main(string[] args)
{
const int x = 100; //常量只能赋值一次,还有初始化器也必须带着,不可以分开
Console.WriteLine(x);
x = 200; //再次赋值会报错
}
}
class Student
{
public uint ID; //一但变量在内存中分配好后,内存块就会全部刷成0,默认值就变成0
public ushort Score; //添加public,就可以从外部看到
}
}
7、装箱与拆箱(Boxing & Unboxing)
*:装箱与拆箱都会损失程序的性能。
namespace TypelnCSharp
{
class Program
{
static void Main(string[] args)
{
int x = 100; //(1)变量存储在栈中
object obj; //(2)obj是个引用类型,分配4个字节
obj = x; //(3)在堆上找个可存储空间,把空间变成一个对象,然后把对象的地址存储到obj的空间中去
/*:2和3就是装箱,即object obj = x;
装箱就是把栈上面值类型的值封装成一个object类型的实例放到堆上;
拆箱:把堆上的object类型的实例里面的值,按照要求拆成目标数据类型,然后存储到栈上去;
*/
int y = (int)obj;
Console.WriteLine(y);
}
}
class Student
{
public uint ID; //一但变量在内存中分配好后,内存块就会全部刷成0,默认值就变成0
public ushort Score; //添加public,就可以从外部看到
}
}
五、方法的定义、调用与调试:
1、方法的由来:
(1)方法(method)的前身是C/C++语言的函数(function):
方法是面向对象范畴的概念,在面向对象语言中仍然称为函数。
使用C/C++语言做对比。
(2)永远都是类(或结构体)的成员:
C#语言中函数不可能独立于类(或结构体)之外。
只有作为类(或结构体)成员时才被称为方法。
C++中是可以的,称为“全局变量”
(3)是类(或结构体)最基本的成员之一:
最基本的成员只有两个——字段和方法(成员变量与成员函数),本质还是数据+算法
方法表示类(或结构体)“能做什么事情”
(4)为什么需要方法和函数:
目的1:隐藏复杂的逻辑
目的2:把大算法分解为小算法
目的3:复用(reuse,重用)
示例:计算圆面积、圆柱体积、圆锥体积
namespace CSharpMethodExample
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
Console.WriteLine(c.GetCircleArea(10));
Console.WriteLine(c.GetConeVolume(10,5));
}
}
class Calculator
{
//自顶向下,逐步求精的算法
public double GetCircleArea(double r) //方法头。方法1:求底部圆面积
{
return Math.PI * r * r; //方法体
}
public double GetCylinderVolume(double r, double h) //方法2:求圆柱体积
{
return GetCircleArea(r) * h;
}
public double GetConeVolume(double r, double h) //方法3:求圆锥体积
{
return GetCylinderVolume(r,h) / 3;
}
}
}
2、方法的定义与调用:
(1)声明方法的语法详解
参见C#语言文档(声明/定义不分家)
Parameter全称为“formal parameter”,形式上的参数称为“形参”
Parameter是一种变量
(2)为方的命名规范
大小写规范
需要以动词或者动词短语作为名字
(3)重温静态(static)方法和实例方法
public static double GetCircleArea(double r) //静态方法
public double GetCircleArea(double r) //实例方法
(4)调用方法
Argument中文C#文档的官方译法为“实际参数”,简称“实参”,可理解为调用方法时的真实条件
调用方法时的argument列表要与定义方法时的parameter列表相匹配。[C#是强类型语言,argument是值、 parameter是变量,值与变量一定要匹配,不然编译器会报错]
namespace CSharpMethodExample
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
double result = Calculator.GetCylinderVolume(3.0,4.0);
Console.WriteLine(result);
}
}
class Calculator
{
//自顶向下,逐步求精的算法
public static double GetCircleArea(double r) //方法头。方法1:求底部圆面积
{
return Math.PI * r * r; //方法体
}
public static double GetCylinderVolume(double r, double h) //方法2:求圆柱体积
{
return GetCircleArea(r) * h;
}
public static double GetConeVolume(double r, double h) //方法3:求圆锥体积
{
return GetCylinderVolume(r,h) / 3;
}
}
}
3、构造器(一种特殊的方法):
(1)构造器(constructor)是类型的成员之一;[*类型成员:字段、方法、属性、事件…10多种]。
(2)狭义构造器指的是“实例构造器”(instance constructor):构建实例内部内存结构。
(3)如何调用构造器:
(4)声明构造器
namespace ConstructorExample
{
class Program
{
static void Main(string[] args)
{
//声明变量:Student stu,创建实例:new Student();其中“()”就在调用构造器
Student stu = new Student(1, "Mr.Okay");
Console.WriteLine(stu.ID);
Console.WriteLine(stu.Name);
Console.WriteLine("==================");
Student stu2 = new Student();
Console.WriteLine(stu2.ID);
Console.WriteLine(stu2.Name);
}
}
class Student
{
//*注:敲出“ctor”,然后敲击两次“Tab”键,就会直接生成构造器模板
public Student() //自定义一个构造器。函数名必须与类名完全一致。括号里面是参数列表,没有就空着。
{
this.ID = 1; //this:指的是实例自己
this.Name = "No name";
}
public Student(int initId, string initName)
{
this.ID = initId;
this.Name = initName;
}
//创建两个公有的字段
public int ID; //默认构造器把ID和Name初始化为0。将内存里的对象里的字段初始化进行构造了
public string Name;
}
}
(5)构造器的内存原理
4、方法的重载:
(1)调用重载方法的示例
(2)声明带有重载的方法
[1]方法签名(method signature)由方法的名称、类型形参的个数和它的每一个形象(按从左到右的顺序)的类 型和种类(值、引用或输出)组成。方法签名不包含返回类型。
namespace OverloadExample
{
class Program
{
static void Main(string[] args)
{
}
}
class Calculator
{
public int Add(int a, int b) //(1)先创建一个方法
{
return a + b;
}
public int Add(ref int a,int b) //参数的种类不同,方法签名就会不同,可以编译通过(ref int a:引用参数)
{
return a + b;
}
public int Add(out int a, int b) //参数的种类不同,方法签名就会不同,可以编译通过(out int a:输出参数)
{
a = 100;
return a + b;
}
public int Add(int a, int b, int c) //参数数量不同,方法签名就会不同
{
return a + b + c;
}
public double Add(double a, double b) //改参数的类型,方法签名不同,可以编译过去
{
return a + b;
}
public int Add(int a, int b) //多了一个类型形参,方法签名不同,可以编译过去。
{ //类型形参:里面的T代表将来会有一个类型参与到方法里来
T t; //声明一个t的变量...
return a + b;
}
}
}
[2]实例构造函数签名由它的每一个形参(从左到右的顺序)的类型和种类(值、引用或输出)组成。
[3]重载决策(到底调用哪一个重载):用于在给定了参数列表和一组候选函数成员的情况下,选择一个最佳函数 成员来实施调用。在调用方法的时候,根据传递参数的类型来决定调用哪一个方法重载。
namespace OverloadExample
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
double result = c.Add(100D,200D); //根据里面传递的参数来决定选择哪一个方法重载
Console.WriteLine(result);
}
}
class Calculator
{
public int Add(int a, int b) //(1)先创建一个方法
{
return a + b;
}
public int Add(int a, int b, int c) //参数数量不同,方法签名就会不同
{
return a + b + c;
}
public double Add(double a, double b) //改参数的类型,方法签名不同,可以编译过去
{
return a + b;
}
public int Add(int a, int b) //多了一个类型形参,方法签名不同,可以编译过去。
{ //类型形参:里面的T代表将来会有一个类型参与到方法里来
T t; //声明一个t的变量...
return a + b;
}
}
}
5、如何对方法进行Debug
(1)设置断点(breakpoint);
(2)观察方法调用时的call stack;
call stack:调用堆栈
最顶层是当前所指方法,下一行是哪里调用这个方法的
(3)Step-in, Step-over, Step-out;
Step-in:F11,逐句查看,小范围细致查找Bug;
Step-over:F10,跳过过程直接到达结果处,快速大范围的定位Bug位置;
Step-out:(shift+F11),回到调用这个方法的地方。
(4)观察局部变量的值与变化;
在局部变量小窗口观察值;
也可以鼠标放到变量上进行查看。
6、方法的调用与栈(*难点)
(1)方法调用时内存的分配
对stack frame的分析
stack frame:当一个方法被调用时,在栈内存当中的布局。(谁调用,就谁负责压栈)
六、操作符
1、操作符概述
(1)操作符(Operator):也译为“运算符”。
操作符是用来操作数据的,被操作符操作的数据称为操作数(Operand)。
下表按照优先级进行排序:(由高到低)【*注意:数值提升】
类别 运算符
基本 x.y f(x) a[x] x++ x-- new typeof default checked unchecked delegate sizeof ->
一元 + - ! ~ ++x --x (T)x await &x x
乘法 * / %
加减 + -
移位 << >>
关系和类型检测 < > <= >= is as
相等 == !=
逻辑“与” &
逻辑 XOR ^
逻辑 OR |
条件 AND &&
条件 OR ||
Null 合并 ??
条件 ?:
赋值和lambda = = /= %= += -= <<= >>= &= ^= |= =>
2、操作符的本质
(1)操作符的本质是函数(即算法)的“简记法”。
假如没有发明“+”、只有Add函数,算法3+4+5将可以写成Add(Add(3,4),5);
假如没有发明“”、只有Mul函数,算法3+45将可以写成Add(3,Mul(4,5)),注意优先级。
(2)操作符不能脱离与它关联的数据类型
可以说操作符就是与固定数据类型相关联的一套基本算法的“简记法”。
实例:为自定义数据类型创建操作符。
namespace CreatorOperatpr
{
class Program
{
static void Main(string[] args)
{
Person person1 = new Person();
Person person2 = new Person();
person1.Name = "Deer";
person2.Name = "Deer's wife";
List nation = Person.GetMarry(person1,person2);
foreach (var p in nation)
{
Console.WriteLine(p.Name);
}
}
}
class Person
{
public string Name;
public static List GetMarry(Person p1, Person p2) //List:返回值,返回一组Person
{
List people = new List();
people.Add(p1);
people.Add(p2);
for (int i = 0; i < 11; i++)
{
Person child = new Person();
child.Name = p1.Name + "&" + p2.Name+"'s child";
people.Add(child);
}
return people;
}
}
}
3、操作符的优先级
(1)可以使用圆括号提高被括起来表达式的优先级;
(2)圆括号可以嵌套;
(3)不像数学里有方括号和花括号,在C#语言中“[]”与“{}”有专门的用途。
[]:访问索引器
{}:名称空间、类空间、方法的body
4、同级操作符的运算顺序
(1)除了带有赋值功能的操作符,同优先级操作符都由左向右进行运算;
(2)带有赋值功能的操作符的运算顺序是由右向左;
namespace OperatorPriority
{
class Program
{
static void Main(string[] args)
{
int x = 100;
int y = 200;
int z = 300;
x += y += z;
Console.WriteLine(x); //600
Console.WriteLine(y); //500
Console.WriteLine(z); //300
}
}
}
(3)与数学运算不同,计算机语言的同优先级运算没有“结合律”;
3+4+5只能理解为Add(Add(3,4),5)不能理解为Add(3,Add(4,5))。
5、基本操作符
(1)表达式:表达一定算数意图的式子。基本表达式就是不能再拆分的表达式。
(2)x.y:”.”叫做成员访问操作符,
功能:能够访问外层命名空间中的子级命名空间,
能访问名称空间中的类型,
能访问类型的静态成员,
能访问对象的成员。
namespace OperatorExample
{
class Program
{
static void Main(string[] args)
{
System.IO.File.Create("D:\\HelloWorld.txt"); //外层命名空间.子级命名空间.类.静态成员
}
}
}
namespace OperatorExample
{
class Program
{
static void Main(string[] args)
{
Form myForm = new Form(); //创建对象并赋给myForm
myForm.Text = "Hello world!"; //使用myForm的属性TEXT
myForm.ShowDialog(); //使用ShowDialog方法
}
}
}
(3)f(x):方法调用操作符,里面的x是参数
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
Action myAction = new Action(c.PrintHello); //委托
myAction(); //Hello
double x = c.Add(3.0,4.0);
Console.WriteLine(x); //8
}
}
class Calculator
{
public double Add(double a, double b)
{
return a + b;
}
public void PrintHello()
{
Console.WriteLine("Hello");
}
}
}
(4)a[x]:元素访问操作符,访问元素
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int[] myIntArray = new int[5]{1,2,3,4,5}; //创建一个数组
Console.WriteLine(myIntArray[0]); //输出数组第一个数
Console.WriteLine(myIntArray[4]); //输出第五个
Console.WriteLine(myIntArray[myIntArray.Length - 1]); //永远不会超出边界
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
//string:当作索引类型,Student:当作值类型
Dictionary stuDic = new Dictionary(); //带有尖括号的,叫做泛型类:泛型类不是完整的类,必须要和其他类型组合在一起,才是完整的类
for (int i = 1; i < 100; i++)
{
Student stu = new Student();
stu.Name = "s_" + i.ToString();
stu.Score = 100 + i;
stuDic.Add(stu.Name, stu); //stu.Name:当作索引,stu当作值
}
Student number6=stuDic["s_6"]; //元素访问操作符,[]中放的是集合的索引,这个索引不一定都是整数
Console.WriteLine(number6.Score);
}
}
class Student
{
public string Name;
public int Score;
}
}
(5)自增自减:x++,x–:
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int x;
int y;
x = 100;
y = x++; //首先x的值赋给y,x再减1:y=x,x=x-1
Console.WriteLine(x); //101
Console.WriteLine(y); //100
}
}
}
(6)Typeof:查看一个类型的内部结构(Metadata)
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Type t = typeof(int);
Console.WriteLine(t.Namespace);
Console.WriteLine(t.FullName);
Console.WriteLine(t.Name);
//查看int有多方法:
int c = t.GetMethods().Length;
foreach (var mi in t.GetMethods())
{
Console.WriteLine(mi.Name);
}
Console.WriteLine©;
}
}
}
(7)default:
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int x = default(int); //如果default发现操纵的是结构体类型的话,就会返回内存块为0的值
Console.WriteLine(x); //0
Form myForm = default(Form); //default发现操作的是引用类型时,会返回null(空值)
Console.WriteLine(myForm==null); //TRUE
Level level=default(Level); //当枚举里面没赋值时,返回第一个,赋值后返回最小的。
Console.WriteLine(level);
}
}
enum Level //声明一个枚举类型:设计枚举类型的时候,最好设计一个为0
{
Low = 1,
Mid = 1,
High = 2
}
}
(8)NEW:在内存当中帮助我们创建实例,并且调用实例构造器,可以把这个实例的内存地址赋值给变量。
如:new Form(); //创建实例构造器
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int a = 100; //int:显式变量
var x = 100; //var:隐式变量;赋值为int类型
var y = 100L; //赋值为INT64
var z = 100D; //double类型
z = “100”; //z已经定义为double类型了,不能赋值string类型的数据
Console.WriteLine(a.GetType().Name);
Console.WriteLine(x.GetType().Name);
Console.WriteLine(y.GetType().Name);
Console.WriteLine(z.GetType().Name);
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
//new调用初始化器和构造器
Form myForm = new Form() { Text = “Hello”, FormBorderStyle=FormBorderStyle.SizableToolWindow }; //调用初始化器
//Form myForm = new Form() 调用构造器
//myForm.Text = “Hello”;
myForm.ShowDialog();
/*可以执行。new和.操作符的优先级相同,从左至右运算。先创建实例并调用构造器,再引用ShowDialog()方法。
只是没用变量引用这个实例,很快就被垃圾收集器回收了。
*/
new Form() { Text = "Hello"}.ShowDialog();
int x = 100;
string y = "time";
string name = new string();
//
int[] myArray = new int[10];
int[] myArray1 = { 1, 2, 3, 4 }; //不用NEW操作符。这是语法糖衣,在创建某些实例时可以使用new操作符,也可以不用
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
/var和new联用,为匿名类型创建对象,并且用隐式类型变量来引用实例/
var myForm = new Form() { Text = “Hello”}; //Program类和Form形成了紧耦合。Form如果有问题,那么这个类就会出问题。
var person = new { Name = “Mr.Okay”, Age = 34 }; //不知道对象是什么类型
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
Console.WriteLine(person.GetType().Name);
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student(); //这里的 new 是操作符
stu.Report();
CsStudent csStu = new CsStudent();
csStu.Report();
}
}
class Student
{
public void Report()
{
Console.WriteLine("I'm a student.");
}
}
class CsStudent:Student
{
/*
* 如果类里面什么都不写,它就自动继承了父类所有的内容
*/
new public void Report() //子类对父类的方法进行隐藏。 这里的new是修饰符。
{
Console.WriteLine("I'm a csStudent."); //子类对父类的方法进行隐藏
}
}
}
(9)checked和unchecked:检查一个值在内存中是否有溢出。
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2); //32位的二进制表示
Console.WriteLine(binStr);
//uint y = x + 1; //超过最大值,溢出归零
//uint y = checked(x + 1); //使用checked检测是否溢出
//Console.WriteLine(y);
//try
//{
// uint y = checked(x + 1); //使用checked检测是否溢出。使用unchecked就不会抛出溢出异常
// Console.WriteLine(y);
//}
//catch (OverflowException ex)
//{
// Console.WriteLine("There's overflow!!!");
//}
checked
{
try
{
uint y = checked(x + 1);
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("There's overflow!!!");
}
}
}
}
}
(10)delegate操作符:(delegate也是一个关键字,委托)
namespace Example
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//通过+=挂接了一个事件处理器myButton_Click
//this.myButton.Click += myButton_Click;
//void myButton_Click(object sender, RoutedEventArgs e) //1、有名字的函数
//{
// throw new NotImplementedException();
//}
//this.myButton.Click += delegate(object sender, RoutedEventArgs e) //2、delegate操作符声明匿名方法,没名字(过时技术)
//{
// this.myTextBox.Text = "Hello,World!";
//};
this.myButton.Click += (object sender, RoutedEventArgs e) => //3、拉姆达表达式,没名字(现在常用)
{
this.myTextBox.Text = "Hello,World!";
};
}
}
}
(11)sizeof操作符:获取一个对象在内存当中的所占字节数的尺寸。
注意:1、默认情况下,只能用于去获取基本数据类型它们的实例在内存当中所占的字节数
基本数据类型:C#关键字里面的结构体数据类型,除了string,object不是结构体,就不能获取;
像uint,long,ulong,double等就可以使用。
2、在非默认的情况下,我们可以使用sizeof获取自定义的结构体类型的实例在内存当中占的字节数,
但是需要放在不安全的上下文当中。
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int z = sizeof(int);
int x = sizeof(decimal); //decinal:16字节,比double更加精准,常用在金融计算中
Console.WriteLine(z);
Console.WriteLine(x);
unsafe
{
int y = sizeof(Student); //sizeof获取自定义结构体中的字节数,需要放在不安全上下文当中
Console.WriteLine(y);
}
}
}
struct Student
{
int ID;
long Score;
}
}
(12)->:直接操作内存,需要放在不安全的上下文当中。(像指针操作、取地址操作、指针访问成员操作,只能用来操作结构体类型,不能操作引用类型)
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
unsafe
{
Student stu;
stu.ID = 1; //.:直接访问
stu.Score = 99; //直接访问
Student* pStu = &stu;
pStu->Score = 100; //->:通过指针的间接访问
Console.WriteLine(stu.Score);
}
}
}
struct Student
{
public int ID;
public long Score;
}
}
6.一元操作符
(1)&x:取地址操作符,需要在不安全上下文中
x:取引用符号,需要在不安全上下文中
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
unsafe
{
Student stu;
stu.ID = 1; //.:直接访问
stu.Score = 99; //直接访问
Student pStu = &stu; //取地址,&取地址给指针
pStu->Score = 100; //->:通过指针的间接访问
(pStu).Score = 1000; //:取引用
Console.WriteLine(stu.Score);
}
}
}
struct Student
{
public int ID;
public long Score;
}
}
(2)+ - ! ~:正 负 非 反
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(int.MaxValue); //2147483647
Console.WriteLine(int.MinValue); //-2147483648
int x = int.MinValue;
int y = checked(-x); //已经溢出
Console.WriteLine(x);
Console.WriteLine(y);
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int x = int.MinValue;
int y = -x;
Console.WriteLine(y); //-2147483648
string xStr = Convert.ToString(x, 2).PadLeft(32, ‘0’);
Console.WriteLine(xStr); //10000000000000000000000000000000
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int x = 12345678;
int y = ~x; //取反,-12345679
string xStr = Convert.ToString(x, 2).PadLeft(32, ‘0’);
string yStr = Convert.ToString(y, 2).PadLeft(32, ‘0’);
Console.WriteLine(xStr); //00000000101111000110000101001110
Console.WriteLine(yStr); //11111111010000111001111010110001
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
bool b1 = true;
bool b2 = !b1; //!:非操作符,!true=false
Console.WriteLine(b2);
}
}
}
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student(null);
Console.WriteLine(stu.Name);
}
}
class Student
{
public string Name;
public Student(string initName)
{
if (!string.IsNullOrEmpty(initName))
{
this.Name = initName;
}
else
{
throw new ArgumentException("initName cannot be null or empty.");
}
}
}
}
(3)x++,x–:自增自减,尽量单独使用它们,它们的可读性很差
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int x = 100;
–x; //99
x–; //99
++x; //101
x++; //101
int y = x–; //x=99,y=100
int y = --x; //x=99,y=99
Console.WriteLine(x);
}
}
}
(4)(T)x,await:
类型转换:
(1)隐形(implicit)类型转换
[1]不丢失精度的转换
从sbyte 到short、int、long、float、double或decimal。
从byte 到short、ushort、int、uint、long、ulong、float、double或decimal。
从short 到int、long、float、double或decimal。
从ushort 到int、uint、long、ulong、float、double或decimal。
从int 到long、float、double或decimal。
从uint 到long、ulong、float、double或decimal。
从long 到float、double或decimal。
从ulong 到float、double或decimal。
从char 到ushort、int、uint、long、ulong、float、double或decimal。
从float 到double。
从int、uint、long或ulong到float的转换以及从long或ulong到double的转换可能导致精度损失,但绝不会影响数值大小。 其他的隐式数值转换绝不会丢失任何信息。
不存在向char类型的隐式转换,因此其他整形的值不会自动转换为char类型。
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
int x = int.MaxValue;
long y = x; //不丢失精度的类型转换
Console.WriteLine(y);
}
}
}
[2]子类向父类转换(所有的面向对象语言都支持这个转换)
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
/*
子类向父类的隐形转换,teacher->Human->Animal
*/
Teacher t = new Teacher();
t.Eat();
t.Think();
t.Teach();
Human h = t;
h.Eat();
h.Think();
Animal a = h;
a.Eat();
}
}
class Animal
{
public void Eat()
{
Console.WriteLine("Eating...");
}
}
class Human:Animal
{
public void Think()
{
Console.WriteLine("Who i am?");
}
}
class Teacher:Human
{
public void Teach()
{
Console.WriteLine("I teaching .");
}
}
}
[3]装箱
(2)显式(explicit)类型转换
[1]有可能丢失精度(甚至发生错误)的转换,即cast
显示数值转换是指从一个numeric-type到另一个numeric-type的转换,此转换不能已知的隐式数值转换实现,它包括:
从sbyte到byte、ushort、uint、ulong或char。
从byte到sbyte和char。
从short到sbyte、byte、ushort、uint、ulong或char。
从ushort到sbyte、byte、short或char。
从int到sbyte、byte、short、ushort、uint、ulong或char。
从uint到sbyte、byte、short、ushort、int或char。
从long到sbyte、byte、short、ushort、int、uint、ulong或char。
从ulong到sbyte、byte、short、ushort、int、uint、long或char。
从char到sbyte、byte或short。
从float到sbyte、byte、short、ushort、int、uint、long、ulong、char或decimal。
从double到sbyte、byte、short、ushort、int、uint、long、ulong、char、float或decimal。
从decimal到sbyte、byte、short、ushort、int、uint、long、ulong、char、float或double。
由于显示转换包括隐式或显示数值转换,因此总是可以使用强制转换表达式从任何numeric-type转换为任何其他的numeric-type。
[2]拆箱
[3]使用Covert类
[4]ToString方法与各种数据类型的Parse/TryParse方法
namespace Convert
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btn_Click(object sender, RoutedEventArgs e)
{
//double x = System.Convert.ToDouble(tb1.Text);
//double y = System.Convert.ToDouble(tb2.Text);
double x = double.Parse(this.tb1.Text);
double y = double.Parse(this.tb2.Text);
double result = x + y;
//this.tb3.Text = System.Convert.ToString(result);
this.tb3.Text = result.ToString();
}
}
}
(3)自定义类型转换操作符
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
Stone stone = new Stone();
stone.Age = 5000;
Monkey wukongSun = (Monkey)stone; //显式类型转换(变成隐式类型转换:删去“(Monkey)”)
Console.WriteLine(wukongSun.Age);
}
}
class Stone
{
public int Age;
//显示类型转换操作符 就是一个目标类型实例的构造器,写在被转换的类中
//explicit:显式类型转换; operator:因为是操作符,需要加上这个关键字(变成隐式类型转换:explicit改为implicit)
public static explicit operator Monkey(Stone stone)
{
Monkey m = new Monkey();
m.Age = stone.Age / 500;
return m;
}
}
class Monkey
{
public int Age;
}
}
(5)* / % + -:(算数运算符)
[1]*左右两边的类型为整型时,就是整型乘法
int operator *(int x, int y);
uint operator *(uing x, uing y);
long operator *(long x, long y);
ulong operator *(ulong x,);
[2]*左右两边的类型为浮点型时,就是浮点乘法
float operator *(float x,float y);
double operator (double x,double y);
下表列出了非零有限制、零、无穷大、和NaN(not a number)的可能组合结果。x和y是正有限制,z是xy的结果。如果结果对目标类型而言太大,则z为无穷大,反之则无穷小。
+y -y +0 -0 +∞ -∞ NaN
+x +z -z +0 -0 +∞ -∞ NaN
-x -z +z -0 +0 -∞ +∞ NaN
+0 +0 -0 +0 -0 NaN NaN NaN
-0 -0 +0 -0 +0 NaN NaN NaN
+∞ +∞ -∞ NaN NaN +∞ -∞ NaN
-∞ -∞ +∞ NaN NaN -∞ +∞ NaN
NaN NaN NaN NaN NaN NaN NaN NaN
[3]小数(金融)乘法
decimal operator *(decimal x, decimal y);
如果结果值太小,不能用decimal格式表示,则将引发Systems.OverflowException。如果结果值太小,无法用decimal格式表示,则结果为零。在进行任何舍入之前,结果的小数位数是两个操作数的小数位数的和。
[4]整数除法:
int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x,ulong y);
如果右操作数的值为零,则引发System.DivideByZeroException异常。
除法将结果舍入到零。因此,结果的绝对值是小于或等于两个操作数的商的绝对值的最大可能整数。当两个操作数符号相同时,结果为零或正;当两个操作数符号相反时,结果为零或负。
如果左操作数为最小可表示int或long值,右操作数为-1,则发生溢出。在checked上下文中,这会导致引发System.ArithmeticException(或其子类)。在unchecked上下文中,它由实现定义为或者引发System. ArithmeticException(或其子类),或者不以左操作数的结果值报告溢出。
[5]浮点除法:
float operator /(float x, float y);
double operator /(double x, double y);
下表列出了非零有限制、零、无穷大和NaN的所有可能组合的结果。在该表中,x和y是正有限值,z是x/y的结果。如果结果对目标类型而言太小,则z为无穷大。如果结果对目标类型而言太小,则z为零。
+y -y +0 -0 +∞ -∞ NaN
+x +z -z +∞ -∞ +0 -0 NaN
-x -z +z -∞ +∞ -0 +0 NaN
+0 +0 -0 NaN NaN +0 -0 NaN
-0 -0 +0 NaN NaN -0 +0 NaN
+∞ +∞ -∞ +∞ -∞ NaN NaN NaN
-∞ -∞ +∞ -∞ +∞ NaN NaN NaN
NaN NaN NaN NaN NaN NaN NaN NaN
[6]小数除法:
decimal operator /(decimal x, decimal y);
如果右操作数的值为零,则引发System.DivideByZeroException异常。如果结果值太大,不能用decimal格式表示,则将引发System.OverflowException。如果结果值太小,无法用decimal格式表示,则结果为零。结果的小数位数是最小的小数位数,它保留等于最接近真实算数结果的可表示小数值的结果。小数除法等效于使用System.Decimal类型的除法运算符。
[7]整数余数:
int operator %(int x, int y);
uint operator %(uint x, uint y);
long operator %(long x, long y);
ulong operator %(ulong x, ulong y);
x % y的结果是由x-(x/y)*y生成的值。如果y为零,则将引发System.DivideByZeroException异常。
如果左侧的操作数是最小的int或long值,且右侧的操作数是-1,将引发System.OverflowException。只有x % y不引发异常,x / y也不会引发异常。
[8]浮点余数:
float operator %(float x, float y);
double operator %(double x, double y);
下表列出了非零有限值、零、无穷大和NaN的所有可能组合的结果。在该表中,x和y是有限的正值。z是x%y的结果,按照 x – n * y进行计算,其中n是小于或等于x/y的最大可能整数。这种计算余数的方法类似于用于整数操作数的方法。
+y -y +0 -0 +∞ -∞ NaN
+x +z +z NaN NaN x x NaN
-x -z -z NaN NaN -x -x NaN
+0 +0 +0 NaN NaN +0 +0 NaN
-0 -0 -0 NaN NaN -0 -0 NaN
+∞ NaN NaN NaN NaN NaN NaN NaN
-∞ NaN NaN NaN NaN NaN NaN NaN
NaN NaN NaN NaN NaN NaN NaN NaN
[9]小数余数:
decimal operator %(decimal x, decimal y);
如果右操作数为零,则引发System.DivideByZeroException异常。在进行任何舍入之前,结果的小数位数是两个操作数中较大的小数位数,而且结果的符号与x的相同(如果非零)。
[10]整数加法:
int operator +(int x, int y);
uint operator +(uint x, uint y);
long operator +(long x, long y);
ulong operator +(ulong x, ulong y);
[11]浮点加法
float operator +(float x, float y);
double operator +(double x, double y);
y +0 -0 +∞ -∞ NaN
x z x x +∞ -∞ NaN
+0 y +0 +0 +∞ -∞ NaN
-0 y +0 -0 +∞ -∞ NaN
+∞ +∞ +∞ +∞ +∞ NaN NaN
-∞ -∞ -∞ -∞ NaN -∞ NaN
NaN NaN NaN NaN NaN NaN NaN
[12]小数加法:
decimal operator +(decimal x, decimal y);
[13]整数减法:
int operator -(int x, int y);
uint operator -(uint x, uint y);
long operator -(long x, long y);
ulong operator -(ulong x, ulong y);
[14]浮点减法:
float operator -(float x, float y);
double operator -(double x, double y);
y +0 -0 +∞ -∞ NaN
x z x x -∞ +∞ NaN
+0 -y +0 +0 -∞ +∞ NaN
-0 -y -0 +0 -∞ +∞ NaN
+∞ +∞ +∞ +∞ NaN +∞ NaN
-∞ -∞ -∞ -∞ -∞ NaN NaN
NaN NaN NaN NaN NaN NaN NaN
[15]小数减法:
decimal operator +(decimal x, decimal y);
(6)<< >>:左位移,右位移。左移是把数乘2,右移是把数除2。
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
int x = 7;
int y = x << 2;
string strX = Convert.ToString(x, 2).PadLeft(32, ‘0’);
string strY = Convert.ToString(y, 2).PadLeft(32, ‘0’);
Console.WriteLine(strX);
Console.WriteLine(strY);
}
}
}
(7)关系操作符:
关系和类型检测:"< > <= >=" “is as”
相等: == !=
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
int x = 5;
double y = 4.0;
var result = x > y;
Console.WriteLine(result.GetType().FullName); //System.Boolean
Console.WriteLine(result); //TRUE
}
}
}
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
char char1 = ‘a’;
char char2 = ‘A’;
ushort u1 = (ushort)char1; //97
ushort u2 = (ushort)char2; //65
Console.WriteLine(u1);
Console.WriteLine(u2);
var result = char1 > char2; //true
Console.WriteLine(result);
}
}
}
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
string str1 = “abc”;
string str2 = “Abc”;
Console.WriteLine(str1==str2);
}
}
}
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
object o = new Teacher();
//if (o is Teacher)
//{
// Teacher t = new Teacher();
// t.Teach();
//}
Teacher t = o as Teacher;
if (t!=null)
{
t.Teach();
}
}
}
class Animal
{
public void Eat()
{
Console.WriteLine("Eating...");
}
}
class Human : Animal
{
public void Think()
{
Console.WriteLine("Who i am?");
}
}
class Teacher : Human
{
public void Teach()
{
Console.WriteLine("I teaching .");
}
}
class Car
{
public void Run()
{
Console.WriteLine("Runing...");
}
}
}
(8)逻辑运算符:
& :与。 ^ : XOR。 | :OR。
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
//按位求与
int x = 23;
int y = 35;
int z = x & y;
string strX = Convert.ToString(x, 2).PadLeft(32, ‘0’);
string strY = Convert.ToString(y, 2).PadLeft(32, ‘0’);
string strZ = Convert.ToString(z, 2).PadLeft(32, ‘0’);
Console.WriteLine(strX);
Console.WriteLine(strY);
Console.WriteLine(strZ);
}
}
}
(9)条件运算符:
&&:AND。 ||:OR。
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
//按位求与
int x = 23;
int y = 35;
int a = 3;
if (x>y && ++a>3) //||:或的左边为true,就不会运行右边。&&:左边为false,右边就不会运行。(这叫短路效应,要避开)
{
Console.WriteLine(“Hello”);
}
Console.WriteLine(a);
}
}
}
(10)??(null合并)
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
Nullable x = null; //可空类型,可以赋值null
x = 100;
//int? x = 100; //可空类型
//Console.WriteLine(x);
//Console.WriteLine(x.HasValue); //输出是否有值
int? x = null; //x为空
int y = x ?? 1; //如果x为空,就返回1
Console.WriteLine(y);
}
}
}
(11)? :(条件操作符,接受三个参数)
namespace ConversionExample
{
class Program
{
st atic void Main(string[] args)
{
int x = 80;
string str = (x >= 60) ? “Pass” : “Failed”; //if…else…的简写
Console.WriteLine(str);
}
}
}
(12)= *= /= %= += -= <<= >>= &= ^= |= => (赋值和lambda表达式)
(***赋值操作符是从右向左)
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
int x = 5;
x += 1;
Console.WriteLine(x);
int y = 4;
y <<= 2; //左移两位,变成2的4次方
Console.WriteLine(y);
}
}
}
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
int x = 10;
int y = 6;
int z = 7;
int a = x += y *= z; //从右向左运算
Console.WriteLine(a);
}
}
}
七、表达式和语句
1、表达式的定义
(1)什么是表达式:表达式是基本组件之一,而且是最重要的组件。
表达式是语法实体,专门用来求值。
(2)C#语言对表达式的定义:
[1]表达式求值完后的结果为single value(单值),object(对象),method(方法),or namespace(命名空间) 这四类值。
[2]表达式可能由字面值、方法调用、操作符操作数、简单的名字。
简单的名字可能是变量、类型的成员、方法的参数、命名空间或类型名。
[3]表达式是算法逻辑的最基本(最小)单元,表达一定的算法意图。
[4]因为操作符有优先级,所以表达式也就有了优先级。
2、各式表达式概览
(1)C#语言中表达式的分类:
[1]A value.任何能得到值的运算(回顾操作符和结果类型)
Every value has an associated type.每个值都要有自己的类型。
表达式经过运算得出来的值的数据类型,就是这个表达式的数据类型。
namespace ConversionExample
{
class Program
{
static void Main(string[] args)
{
var x = 3 < 5;
if (x) //其中 3<5 就是布尔类型的表达式
{
Console.WriteLine(“OK”);
}
Console.WriteLine(x.GetType().FullName); //System.Boolean。输出x的类型,发现x为布尔类型的值
}
}
}
[2]A variable.一个变量。
Every variable has an associated type.
static void Main(string[] args)
{
int x = 100;
int y;
y = x;
}
[3]A namespace.一个命名空间
static void Main(string[] args)
{
System.Windows.Forms.Form myForm; //System、Windows、Forms三个命名空间都访问方法,这三个都是表达式
}
[4]A type.一个类型
static void Main(string[] args)
{
var t = typeof(Int32); //int32是typeof的操作数,因此typeof就是表达式
}
[5]A method group.例如:Console.WriteLine,这是一组方法,重载决策决定具体调用哪一个
static void Main(string[] args)
{
Console.WriteLine(“Hello,world!”); //Console.WriteLine():会访问一组方法,等输入"Hello,world!"才知道调用哪个
}
[6]A null literal.
static void Main(string[] args)
{
Form myForm = null; //null就是一个表达式,表示空,不是数据类型
int x = 100;
}
[7]An anonymous function.匿名方法
static void Main(string[] args)
{
Action a = delegate() { Console.WriteLine(“Hello,world”); }; //匿名方法表达式,返回值就是一个委托
a();
}
[8]A property access.属性访问
static void Main(string[] args)
{
Form myForm = new Form();
myForm.Text = “Hello”; //myForm.Text:成员访问表达式,调用属性Text
myForm.ShowDialog();
}
[9]An event access.访问某个对象的事件
class Program
{
static void Main(string[] args)
{
Form myForm = new Form();
myForm.Text = “Hello”; //myForm.Text:成员访问表达式,调用属性Text
myForm.Load += myForm_Load; //myForm.Load:访问load事件
myForm.ShowDialog();
}
static void myForm_Load(object sender, EventArgs e)
{
Form form = sender as Form;
if (form ==null)
{
return;
}
form.Text = "New Title";
}
}
[10]An indexer access.访问索引器
static void Main(string[] args)
{
List intList = new List(){ 1, 2, 3 };
int x = intList(2);
}
[11]Nothing.对返回值为void的方法的调用
static void Main(string[] args)
{
Console.WriteLine("Hello"); //不返回任何值
}
(2)复合表达式的求值
注意操作符的优先级和同优先级操作符的运算方向
复合表达式是由一些小的表达式组成的更复杂的表达式。
(3)参考C#语言定义文档
仅作参考,不必深究——毕竟我们是在学习语言、不是去实现这门语言
3、语句的定义
(1)Wikipedia对语句的定义:
[1]In computer programming a statement is the smallest standalone element of an imperative programming language which expresses some action to be carried out.A program written in such a language is formed by a sequence of one or more statements.A statement will have internal components(e.g,expressions).(在计算机编程学科范畴里,语句是命令编程语言[高级语言]中最小的独立元素。语句这种语法实体就是表达一些将被执行的动作[表达式]。一个由高级语言写成的程序,实际上就是一系列的语句构成的。语句还具有自己的内部组件。)
[2]语句是高级语言的语法——编译语言和机器语言只有指令(高级语言中的表达式对应低级语言中的指令),语句等价于一个或一组有明显逻辑关联的指令。举例:求圆柱体积。
(2)C#语言对语句的定义
[1]The actions that a program takes are expressed in statements.Common actions include declaring variables, assigning values, calling methods, looping through collection, and branching to one or another block of code, depending on a given condition. The order in which statements are executed in a program is called the flow of control or flow of execution. The flow of control may vary every time that a pragram is run, depending on how the program reacts to input that it receives at run time.
一个程序所要执行的动作就是以语句的形式来表达的。常用的语句的功能有声明变量、对变量赋值、调用函数、在集合中循环(迭代语句)、根据给定的条件在分支中进行跳转(判断语句)。程序由语句构成,语句执行的顺序叫做控制流和执行流。(输入数据的不同,控制流和执行流的顺序是不同的。)
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
string input = Console.ReadLine();
try
{
double score = double.Parse(input);
if (score>=60)
{
Console.WriteLine(“Pass”);
}
else
{
Console.WriteLine(“Failed”);
}
}
catch
{
Console.WriteLine(“Failed”);
}
}
}
}
[2]C#语言的语句除了让程序员“顺序的”(sequentially)表达算法思想,还能通过条件判断、跳转和循环等方法控制程序逻辑的走向。
[3]简而言之就是:陈述算法思想,控制逻辑走向,完成有意义的动作(action)。
[4]C#语言的语句由分号(;)结尾,但由分号结尾的不一定都是语句。
[5]语句一定是出现在方法体里。
4、语句详解
statement:
labeled-statement(标签语句)
declaration-statement(声明语句)
embedded-statement(嵌入式语句)
embedded-statement:(下面的语句都可以作为嵌入体使用)
block
empty-statement
expression-statement
selection-statement(选择语句)
iteration-statement
jump-statement
try-statement
checked-statement
unchecked-statement
lock-statement
using-statement
yield-statement
(1)局部变量声明:
local-variable-declaration 声明一个或多个局部变量。
Local-variable-declaration:
local-variable-type local-variable-declarators
local-variable-type:
type
var
local-variable-declarators:
local-variable-declarator
local-variable-declarators , local-variable-declarator
local-variable-declarator:
identifier
identifier = local-variable-initializer
local-variable-initializer:
expression
array-initializer
local-variable-declaration的local-variable-type要么直接指定声明引入的变量的类型,要么通过标识符var指示应基于初始值设定项来推断该类型。此类型后接一个local-variable-declarator列表,其中每一项都引入一个新变量。local-variable-declarator由一个命名变量的identifier组成,根据需要此identifier后接一个“=”标记和一个赋予变量初始值的local-variable-initializer。
(2)局部常量声明:
local-constant-declaration用于声明一个或多个局部常量。
local-constant-declaration:
const type constant-declarators
如:const int x = 100;
constant-declarators:
constant-declarator
constant-declarators , constant-declarator
constant-declarator:
identifier = constant-expression
local-constant-declaration的type指定由该声明引入的常量的类型。此类型后接一个constant-declarator列表,其中每一项都引入一个新常量。constant-declarator包含一个命名常量的identifier,后接一个“=”标记,然后是一个对该常量赋值的constant-expression。
(3)表达式语句:
expression-statement用于计算所给定的表达式。由此表达式计算出来的值(如果有)被丢弃。
expression-statement:
statement-expression;
statement-expression:
invocation-expression
object-creation-expression
assignment
post-increment-expression
post-decrement-expression
pre-increment-expression
pre-decrement-expression
await-expression
不是所有的表达式都允许作为语句使用。具体而言,不允许像x + y和x==1这样只计算一个值(此值将被放弃)的表达式作为语句使用。
执行一个expression-statement就是对包含的表达式进行计算,然后将控制转到该expression-statement的结束点。如果一个expression-statement是可到达的,则expression-statement结束点也是可到达的。
(4)选择语句:
选择语句会根据表达式的值从若干个给定的语句中选择一个来执行。
selection-statement:
if-statement
switch-statement
(5)块语句:
block用于只允许使用单个语句的上下文中编写多条语句:
block:
{statement-listopt}
block由一个扩大在大括号的可选statement-listzucheng组成。如果没有此句语句列表,则称块是空的。
块可以包含声明语句。在一个块中声明的局部变量或常量的范围就是该块本身。
在块内,在表达式上下文中使用的名称的含义必须相同。
块语句在方法体中,且块语句是一条语句。
块按下述规则执行:
-如果块是块的,控制转换块的结束点
-如果块不是空的,控制转到语句块列表。当(如果)控制到达语句列表的结束点时,控制转到块的结束点。
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
//一个花括号就是一个块语句,这样写不多见,一般和循环一起写
{
int x = 100;
if (x > 80) Console.WriteLine(x); //嵌入语句
}
//{
//hello: Console.WriteLine("Hello!World!"); //标签语句
//goto hello; //回到标签,形成死循环
//}
//块外面的变量可以被块内使用,但是块内的变量不能被块外所引用
int a = 100;
{
Console.WriteLine(a);
}
}
}
}
(6)选择(判断、分支)语句
选择语句会根据表达式的值从若干个给定的语句中选择一个来执行。
selection-statement:
if-statement
switch-statement
[1]if语句
if语句根据布尔表达式的值选择要执行的语句。
if-statement:
if ( boolean-expression ) embedded-statement
if ( boolean-expression ) embedded-statement else embedded-statement
else部分与语法允许的、词法上最相近的上一个if语句相关联。因而,下列形式的if语句
if (x) if (y) F(); else G();
相当于
if (x) {
if (y) {
F();
}
else {
G();
}
}
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
int x = 100;
int y = 200;
if (x
Console.WriteLine(“Hello!”);
Console.WriteLine(“World”);
}
else
{
Console.WriteLine(“Hello!”);
}
}
}
}
[2]switch语句
switch语句选择一个要执行的语句列表,此列表具有一个相关联的switch标签,它对应与switch表达式的值。
switch-statement:
switch ( expression ) switch-block
switch-block:
{ switch-sectionsopt }
switch-sections:
switch-section
switch-sections switch-section
switch-section:
switch-labels statement-list
switch-labels:
switch-label
switch-labels switch-label
switch-label:
case constant-expression :
default :
switch-statement包含关键字switch,后接带括号的表达式(称为switch表达式),然后是一个switch-block。switch-block包含零个或多个括在大括号内的switch-section。每个switch-section包含一个或多个switch-labels,后接一个statement-list。
switch语句的主导类型(governing type)由switch表达式。
-如果switch表达式的类型为sbyte、byte、short、ushort、int、uint、long、ulong、bool、char、string或 enumtype,或者是对应于以上某种类型的可以为null的类型,则该类型就是switch语句的主导类型。
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
//需求:80-100->A;60-79->B;40-59->C;0-39->D;其他->Error
int score = -1;
switch (score/10)
{
case 10:
if (score==100)
{
goto case 8;
}
else
{
goto default;
}
case 9:
case 8:
Console.WriteLine(“A”);
break;
case 7:
case 6:
Console.WriteLine(“B”);
break;
case 5:
case 4:
Console.WriteLine(“C”);
break;
case 0:
if (score == 0)
{
goto case 1;
}
else
{
goto default;
}
case 1:
case 2:
case 3:
Console.WriteLine(“D”);
break;
default:
Console.WriteLine(“Error”);
break;
}
}
}
}
(7)try语句
try语句提供一种机制,用于捕捉在块的执行期间发生的各种异常。此外,try语句还能让您指定一个代码块,并保证当控制离开try语句时,总是先执行该代码。
Try-statement:
try block catch-clauses
try block finally-clause
try block catch-clauses finally-clause
catch-clauses:
specific-catch-clauses specific-catch-clauseopt
specific-catch-clausesopt specific-catch-clause
specific-catch-clauses:
specific-catch-clause
specific-catch-clauses specific-catch-clause
specific-catch-clause:
catch ( class-type identifieropt ) block
specific-catch-clause:
catch block
finally-clause:
finally block
有三种可能的try语句形式:
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
int r = c.Add(null, “200”);
Console.WriteLine®;
}
}
class Calculator
{
public int Add(string arg1, string arg2)
{
int a = 0;
int b = 0;
bool hasError = false;
try
{
a = int.Parse(arg1);
b = int.Parse(arg2);
}
catch (ArgumentException ane)
{
Console.WriteLine(ane.Message);
hasError = true;
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
hasError = true;
}
catch (OverflowException oe)
{
Console.WriteLine(oe.Message);
hasError = true;
}
finally
{
if (hasError=true)
{
Console.WriteLine(“Excution has error!”);
}
else
{
Console.WriteLine(“Done!”);
}
}
int result = checked(a + b);
return result;
}
}
}
(8)迭代(循环)语句,跳转语句
迭代语句重复执行嵌入语句。
Iteration-statement:
while-statement
do-statement
for-statement
foreach-statement
跳转语句用于无条件的转移控制。
Jump-statement:
break-statement
continue-statement
goto-statement
return-statement
throw-statement
跳转语句将控制转到的位置称为跳转语句的目标(target)。
[1]while语句:
while语句按不同条件执行一个嵌入语句一次或多次。
While-statement:
while ( boolean-expression ) embedded-statement
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
int score = 0;
bool canContinue = true;
Console.WriteLine(“Beggin!!!”);
while (canContinue)
{
Console.WriteLine(“Please input your first number:”);
int x = int.Parse(Console.ReadLine());
Console.WriteLine("Please input your first number:");
int y = int.Parse(Console.ReadLine());
int sum = x + y;
if (sum==100)
{
score++;
Console.WriteLine("Correct! {0}+{1}={2}",x,y,sum);
}
else
{
Console.WriteLine("Error! {0}+{1}={2}",x,y,sum);
canContinue = false;
}
}
Console.WriteLine("Your score is {0}",score);
Console.WriteLine("Game over!");
}
}
}
[2]do…while语句:
do语句按不同条件执行一个嵌入语句一次或多次。
do-statement:
do embedded-statement while ( boolean-expression )
先执行循环体一次,然后再判断是否继续循环。
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
int score = 0;
//bool canContinue = true;
int sum;
Console.WriteLine(“Beggin!!!”);
do
{
Console.WriteLine(“Please input your first number:”);
int x = int.Parse(Console.ReadLine());
Console.WriteLine("Please input your first number:");
int y = int.Parse(Console.ReadLine());
sum = x + y;
if (sum == 100)
{
score++;
Console.WriteLine("Correct! {0}+{1}={2}", x, y, sum);
}
else
{
Console.WriteLine("Error! {0}+{1}={2}", x, y, sum);
//canContinue = false;
}
} while (sum==100);
Console.WriteLine("Your score is {0}",score);
Console.WriteLine("Game over!");
}
}
}
[3]break:立即结束循环语句(只对直接包含自己的那个循环起作用)
break-statement:
break;
[4]continue:结束这一循环,进入下一次循环(只对直接包含自己的那个循环起作用)
continue-statement:
continue;
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
int score = 0;
//bool canContinue = true;
int sum = 100;
Console.WriteLine(“Beggin!!!”);
do
{
Console.WriteLine(“Please input your first number:”);
int x = 0;
try
{
x = int.Parse(Console.ReadLine());
}
catch
{
Console.WriteLine(“First number has problem!Restart.”);
continue;
}
Console.WriteLine("Please input your first number:");
int y = 0;
try
{
y = int.Parse(Console.ReadLine());
}
catch
{
Console.WriteLine("Second number has problem!Restart.");
continue;
}
sum = x + y;
if (sum == 100)
{
score++;
Console.WriteLine("Correct! {0}+{1}={2}", x, y, sum);
}
else
{
Console.WriteLine("Error! {0}+{1}={2}", x, y, sum);
//canContinue = false;
}
} while (sum==100);
Console.WriteLine("Your score is {0}",score);
Console.WriteLine("Game over!");
}
}
}
[5]for语句:
for语句计算一个初始化表达式序列,然后,当某个条件为真时,重复执行相关的嵌入语句并计算一个迭代表达式序列。
for-statement:
for ( for-initializeropt ; for-conditionopt ; for-iteratoropt ) embedded-statement
for-initializer:
local-variable-declaration
statement-expression-list
for-condition:
boolean-expresion
for-iterator:
statement-expression-list
statement-expression-list:
statement-expression
statement-expression-list , statement-expression
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
for (int a = 1; a <= 9; a++)
{
for (int b = 1; b <= a; b++)
{
Console.Write("{0}{1}={2}\t",a,b,ab);
}
Console.WriteLine();
}
}
}
}
[6]foreach语句:
foreach语句用于枚举一个集合的元素,并对该集合中的每个元素执行一次相关的嵌入语句。
foreach-statement:
foreach ( local-variable-type identifier in expression ) embedded-statement
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
List intList = new List() { 1, 2, 3, 4, 5, 6 };
//foreach作用:对集合进行遍历
foreach (var current in intList)
{
Console.WriteLine(current);
}
}
}
}
[7]return语句:尽早return
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
Greeting(“Mr.Okay”);
var result = WholsWho(“Okary”);
Console.WriteLine(result);
}
static string WholsWho(string alias)
{
if (alias == "Mr.Okary")
{
return "Tim";
}
else
{
return "I don't know!";
}
}
static void Greeting(string name)
{ //尽早return,可以使结构更加精短
if (string.IsNullOrEmpty(name))
{
return;
}
Console.WriteLine("Hello {0}!" ,name);
}
}
}
八、字段、属性、索引器、常量
成员 说明
常量 与类关联的常量值
字段 类的变量
方法 类可执行的计算和操作
属性 与读写类的命名属性相关联的操作
索引器 与以数组方式索引类的实例相关联的操作
事件 可由类生成的通知
运算符 类所支持的转换和表达式运算符
构造函数 初始化类的实例或类本身所需的操作
析构函数 在永久丢弃类的实例之前执行的操作
类型 类所声明的嵌套类型
1、字段
(1)什么是字段:
[1]字段(field)是一种表示与对象或类型(类与结构体)关联的变量。field-declarators用于引入一个或多个给定类型字段。字段的定义放在类体里,不能放在函数体中。字段不是语句
field-declaration:
attributesopt field-modifiersopt type variable-declarators ;
field-modifiers:
field-modifier
field-modifiers field-modifier
field-modifier
new
public
protected
internal
private
static
readonly只读
volatile
variable-declarators:
variable-declarator
[2]字段是类型的成员,旧称“成员变量”
[3]与对象关联的字段亦称“实例字段”
[4]与类型关联的字段称为“静态字段”,由static修饰
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
List stuList = new List();
for (int i = 0; i < 100; i++)
{
Student stu = new Student();
stu.Age = 24;
stu.Score = i;
stuList.Add(stu);
}
int totalAge = 0;
int totalScore = 0;
foreach (var stu in stuList)
{
totalAge += stu.Age;
totalScore += stu.Score;
}
Student.AverageAge = totalAge / Student.Amount;
Student.AverageScore = totalScore / Student.Amount;
Student.ReportAmount();
Student.ReportAverageAge();
Student.ReportAverageScore();
}
}
class Student
{
public int Age; //实例字段
public int Score; //实例字段
public static int AverageAge; //静态字段
public static int AverageScore;
public static int Amount;
public Student() //构造函数
{
Student.Amount++; //学生总数加一
}
public static void ReportAmount() //创建静态方法
{
Console.WriteLine(Student.Amount);
}
public static void ReportAverageAge()
{
Console.WriteLine(Student.AverageAge);
}
public static void ReportAverageScore()
{
Console.WriteLine(Student.AverageScore);
}
}
}
(2)字段的声明:
[1]参见C#语言定义文档
[2]尽管字段声明带有分号,但它不是语句
[3]字段的名字一定是名词
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Student.Amount);
}
}
class Student
{
public int Age = 20; //实例字段
public int Score; //实例字段
public static int AverageAge; //静态字段
public static int AverageScore;
public static int Amount = 100; //声明时就被执行;静态实例构造器只会执行一次,就是在运行环境加载数据类型的时候
//static Student() //静态构造器
//{
// Student.Amount = 100;
//}
}
}
(4)只读字段:
[1]实例只读字段
[2]静态只读字段
namespace StatementsExample
{
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student(1);
Console.WriteLine(stu1.ID);
stu1.ID = 2; //只读字段不能再被赋值
}
}
class Student
{
public readonly int ID; //只读实例
public int Age = 20; //实例字段
public int Score; //实例字段
public static int AverageAge; //静态字段
public static int AverageScore;
public static int Amount = 100; //声明时就被执行;静态实例构造器只会执行一次,就是在运行环境加载数据类型的时候
//static Student() //静态构造器
//{
// Student.Amount = 100;
//}
public Student(int id)
{
this.ID = id;
}
}
}
2、属性:
(1)什么是属性:
[1]属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态;属性的示例包括字符串的长度、字体的大小、窗口的标题、客户的名称,等等。属性是字段的自然扩展,此二者都是具有关联类型的命名成员,而且访问字段和属性的语法是相同的。然而,与字段不同,属性不表示存储位置。相反,属性有访问器(accessor),这些访问器指定在它们的值被读取或写入时需执行的语句。因此属性提供了一种机制,它把读取和写入对象的某些特性与一些操作关联起来;甚至,它们还可以对此类特性进行计算。
属性是使用property-declaration声明:
property-declaration:
attributesopt property-modifiersopt type member-name { accessor-declarations }
property-modifiers:
property-modifier
property-modifiers property-modifier
[2]属性是字段的自然扩展
从命名上看,field更偏向于实例对象在内存中的布局,Property更偏向于反映现实世界对象的特征;
对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的;
对内:保护字段不被非法值“污染”;
[3]属性由Get/Set方法对进化而来;
namespace PorpertyExample
{
class Program
{
static void Main(string[] args)
{
try
{
Student stu1 = new Student();
stu1.Age = 20;
Student stu2 = new Student();
stu2.Age = 20;
Student stu3 = new Student();
stu3.Age = 20;
int avgAge = (stu1.Age + stu2.Age + stu3.Age) / 3;
Console.WriteLine(avgAge);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class Student
{
private int age;
public int Age //创建属性(由Get和Set方法进化而来,更加安全)
{
get
{
return this.age;
}
set //在set get上下文中 value 是系统设定的关键词,不需要自己申明
{
if (value>=0 && value<=120)
{
this.age = value;
}
else
{
throw new Exception("Age value has error!");
}
}
}
//public int GetAge() //Get方法
//{
// return this.age;
//}
//public void SetAge(int value) //Set方法
//{
// if (value>=0 && value <=120)
// {
// this.age = value;
// }
// else
// {
// throw new Exception("Age value is error!");
// }
//}
}
}
[4]又一个“语法糖”——属性背后的秘密;
(2)属性的声明:
[1]完整声明——后台(back)成员变量与访问器(注意使用code snippet和refactor工具);
[2]简略声明——只有访问器(查看IL代码);
//完整声明
namespace PorpertyExample
{
class Program
{
static void Main(string[] args)
{
try
{
Student.Amount = -100;
Console.WriteLine(Student.Amount);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class Student
{
//打出propfull再按两次Tab键,就会自动创建属性(有两部分,属性及其支持字段)
private int age;
public int Age
{
get { return age; }
set {
if (value>=0 && value<=120)
{
age = value;
}
else
{
throw new Exception("Age value has error!");
}
}
}
private static int amount;
public static int Amount
{
get { return amount; }
set {
if (value >= 0)
{
Student.amount = value;
}
else
{
throw new Exception("Amount must greater than 0.");
}
}
}
}
}
//简略声明
class Student
{
//简略声明
public int Age { get; set; }
}
[3]动态计算值的属性;
namespace PorpertyExample
{
class Program
{
static void Main(string[] args)
{
try
{
Student stu = new Student();
stu.Age = 16;
Console.WriteLine(stu.CanWork);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class Student
{
private int age; //字段
public int Age //属性
{
get { return age; } //删掉set后变成只读
set { age = value; } //private:私有,只在这个类中可以set(写)到
}
public bool CanWork
{
get
{
if (this.age>=16)
{
return true;
}
else
{
return false;
}
}
}
}
}
[4]注意实例属性和静态属性;
[5]属性的名字一定是名词;
[6]只读属性——只有getter没有setter;
尽管语法上正确,几乎没有人使用“只写属性”,因为属性的主要目的是通过向外暴露数据而表示对象/类型的状态。
namespace PorpertyExample
{
class Program
{
static void Main(string[] args)
{
try
{
Student stu = new Student();
stu.Age = 100;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class Student
{
private int age;
public int Age
{
get { return age; } //删掉set后变成只读
private set { age = value; } //private:私有,只在这个类中可以set(写)到
}
public void SomeMethod()
{
this.Age = 20; //不是只读,它的setter不能从外界访问到
}
}
}
(3)属性与字段的关系:
[1]一般情况下,它们都用于表示实体(对象或类型)的状态。
[2]属性大多数情况下是字段的包装器(wrapper)。
[3]建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的。
3、索引器
(1)什么是索引器:
索引器(indexer)是这样一种成员:它使对象能够用与数组相同的方式(即使用下标)进行索引
(2)索引器的声明:
参见C#语言定义文档
注意:没有静态索引器
namespace IndexerExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student();
stu[“Math”] = 100;
stu[“Math”] = 150; //赋值过,再次赋值就更新值
var mathScore = stu[“Math”];
Console.WriteLine(mathScore); //如果没有存储过Math的值,就是空值;如果有值,就返回值;
}
}
class Student
{
//创建字典类型的私有字段。字典类型:Dictionary<'键', '值'>
private Dictionary scoreDictionary = new Dictionary();
//声明索引器。输入indexer,再敲两次Tab键就可以生成索引器框架代码
public int? this[string subject] //int?:表示返回可空的数据类型。string:用字符串类型进行索引。subject:表示上过的科目
{
get
{ //拿着传进来的科目名称,去字典里面查。如果字典里面有这个值就返回去,没有就返回NULL值
if(this.scoreDictionary.ContainsKey(subject)) //ContainsKey():判断字典里是否有指定的键
{
return this.scoreDictionary[subject]; //返回这个键的内容
}
else
{
return null;
}
}
set
{
if (value.HasValue==false) //如果可空类型没有值,就抛出异常
{
throw new Exception("Score cannot be null.");
}
if (this.scoreDictionary.ContainsKey(subject)) //传进字典里的值不能为空值,需要使用value.Value
{
this.scoreDictionary[subject] = value.Value; //判断字典中是否有这个键,有的话就更新这个键。value表示赋进来的值
}
else
{
this.scoreDictionary.Add(subject, value.Value); //字典中如果没有这个科目的值,就加进去
}
}
}
}
}
4、常量
(1)什么是常量:
[1]常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
[2]常量隶属于类型而不是对象,即没有“实例常量”
“实例常量”的角色由只读实例字段来但当
[3]注意区分成员常量与局部常量
成员常量:类型的成员
局部常量:参与组成方法体中的算法
namespace ConstantExample
{
class Program
{
static void Main(string[] args)
{
int b = int.MaxValue; //int.MaxValue:成员常量
double x = GetArea(1.0);
Console.WriteLine(x);
Console.WriteLine(b);
const int y = 100; //局部常量
y = 200; //报错:常量不可以更改
}
static double GetArea(double r)
{
double a = Math.PI * r * r; //Math.PI:(成员常量)就是个常量值3.14159,提高运行效率
return a;
}
}
}
(2)常量的声明
namespace ConstantExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(WASPEC.WebsiteURL);
}
}
class WASPEC
{
public const string WebsiteURL="http://www.waspec.org";
}
}
(3)各种“只读”的应用场景
[1]为了提高程序可读性和执行效率——常量(高效率)
[2]为了防止对象的值被改变——只读字段(比常量效率低)
[3]向外暴露不允许修改的数据——只读属性(静态或非静态),功能与常量有一些重叠
[4]当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)——静态只读字段(代码如下:)
namespace ConstantExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(WASPEC.WebsiteURL);
}
}
class WASPEC
{
public const string WebsiteURL="http://www.waspec.org";
public static readonly Building Location = new Building("Some Adress");
}
class Building
{
public Building(string address)
{
this.Address = address;
}
public string Address { get; set; }
}
}
九、传值/输出/引用/数组/具名/可选参数,扩展方法
1、传值参数
声明时不带修饰符的形参是值形参。一个值形参对应于一个局部变量,只是它的初始值来自该方法调用所提供的相关实参。
当形参是值形参时,方法调用中的对应实参必须是表达式,并且它的类型可以隐式转换为形参的类型。
允许方法将新值赋给值参数。这样的赋值只影响由该值形参表示的局部存储位置,而不会影响在方法调用时由调用方给出的实参。
[1]值类型的传值参数的用法
注意:值参数创建变量的副本;
对值参数的操作永远不影响变量的值。
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student();
int y = 100;
stu.AddOne(y); //101
Console.WriteLine(y); //100
}
}
class Student
{
public void AddOne(int x) //传值参数,传进来的是副本的值,不改变其它类值
{
x = x + 1;
Console.WriteLine(x);
}
}
}
[2]引用参数的传值参数,并且新创建对象
即 原方法里的值不会改变
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student() { Name = “Tim” }; //调用SomeMethod方法时,两个变量名不一定一样
SomeMethod(stu);
Console.WriteLine("{0},{1}", stu.GetHashCode(), stu.Name); //GetHashCode():每个对象的GetHashCode都不同。
}
static void SomeMethod(Student stu) //这个stu的作用域是SomeMethod方法,与main方法的方法体里的stu作用域不同
{
//方法为对象赋新值,这个新值是新对象在内存当中的地址,这个新对象的Name属性是Tom
stu = new Student() { Name = "Tom" }; //新创建一个实例交给参数进行引用,新实例名字设为Tom
Console.WriteLine("{0},{1}", stu.GetHashCode(), stu.Name); //所有的类型都直接或间接是Object的派生类,因此无论什么类型,都有GetHashCode方法
}
}
class Student
{
public string Name { get; set; } //简化声明属性:向外暴露数据,比字段更安全
}
}
[3]引用类型的传值参数:只操作对象,不创建新对象
注意:对象还是那个对象,但对象里的值(字段/属性)已经改变
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student() { Name = “Tim” };
UpdateObject(stu);
Console.WriteLine(“HashCode={0},Name={1}”, stu.GetHashCode(), stu.Name);
}
static void UpdateObject(Student stu)
{
//只更新对象,而不创建新对象的情形
//我们把这种修改参数引用值的情况叫做某个方法的副作用(side-effect)。这种副作用一般是要避免的,方法返回参数值一般使用return。
stu.Name = "Tom";
Console.WriteLine("HashCode={0},Name={1}", stu.GetHashCode(), stu.Name);
}
}
class Student
{
public string Name { get; set; } //简化声明属性:向外暴露数据,比字段更安全
}
}
2、输出参数
用out修饰符声明的形参是输出形参。类似于引用形参,输出形参不创建新的存储位置(即不创建副本)。相反,输出形参表示的存储位置恰是在该方法调用中作为实参给出的那个变量所表示的存储位置。(是打算将输出参数当作一个值输出。)
当形参为输出形参时,方法调用中的相应实参必须由关键字out并后接一个与形参类型相同的variable-reference组成。变量在可以作为输出参数传递之前不一定需要明确赋值,但是在将变量作为输出形参传递的调用之后,该变量被认为是明确赋值的(即在方法体中要有明确的赋值)。
在方法内部,与局部变量相同,输出形参最初被认为是未赋值的,因而必须在使用它的值之前明确赋值。在方法返回之前,该方法的每个输出形参都必须明确赋值。
声明为分部方法或迭代器的方法不能有输出形参。
输出形参通常用在需要产生多个返回值的方法中。例如:
using System;
class Test
{
static void sqlitpath(string path, out string dir, out string name){
int i = path.Length;
while(i>0){
char ch = path[i – 1];
if(ch == ‘\’ || ch == ‘/’ || ch == ‘:’) break;
i–;
}
dir = path.Substring(0, i);
name = path.Substring(i);
}
static void Main(){
string dir, name;
SplitPath(“c:\Windows\System\hello.txt”, out dir, out name);
Console.WriteLine(dir);
Console.WriteLine(name);
}
}
[1]值类型的输出参数:
注意:输出参数并不创建变量的副本;
方法体内必须有对输出变量的赋值操作;
使用out修饰符显式指出——此方法的副作用是通过参数向外输出值;
从语义上来讲——ref是为了“改变”,out是为了“输出”。
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“Please input first number:”);
string arg1 = Console.ReadLine();
double x = 0;
//TryParse是double类型,因此,需要out关键字返回x值
//需要写出out关键字,显式的提醒我们通过这个参数拿到输出值,并把值传给x,然后由b1接收返回值。
bool b1 = double.TryParse(arg1, out x);
if (b1 == false)
{
Console.WriteLine(“Input error!”);
return;
}
Console.WriteLine("Please input second number:");
string arg2 = Console.ReadLine();
double y = 0;
bool b2 = double.TryParse(arg2, out y);
if (b2==false)
{
Console.WriteLine("Input error!");
return;
}
double z = x + y;
Console.WriteLine("{0}+{1}={2}", x, y, z);
}
}
}
TryParse的逻辑:
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
double x = 100;
bool b = DoubleParser.TryParse(“211”, out x); //输入值错误时,x的值会被覆盖为0,这是正常的
if (b==true)
{
Console.WriteLine(x + 1);
}
else
{
Console.WriteLine(x);
}
}
}
class DoubleParser
{
public static bool TryParse(string input, out double result)
{
try
{
result = double.Parse(input);
return true;
}
catch //不写Exception,所有的错误都抓取
{
result = 0;
return false;
}
}
}
}
[2]引用类型的输出参数:
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student stu = null;
bool b = StudentFactory.Create(“Tom”, 23, out stu); //传进去名字和年龄,输出给stu
if (b==true) //如果创建成功
{
Console.WriteLine(“Student {0}, age is {1}.”, stu.Name, stu.Age);
}
}
}
class Student
{
//创建两个类型
public int Age { get; set; }
public string Name { get; set; }
}
class StudentFactory
{
public static bool Create(string stuName, int stuAge, out Student result) //用输出参数将创建的实例交还回来
{
//逻辑:将传入进来的姓名和年龄,赋给创建的学生实例的属性
result = null;
if (string.IsNullOrEmpty(stuName)) //学生名字如果为空,就返回false
{
return false;
}
if (stuAge<20||stuAge>80)
{
return false;
}
result = new Student() { Name = stuName, Age = stuAge }; //名字和年龄都合法后,创建对象,并将名字和年龄赋值给类型
return true;
}
}
}
3、引用参数
引用参数是用ref修饰符声明的形参。与值形参不同,引用形参并不创建新的存储位置。相反,引用形参表示的存储位置恰是在方法调用中作为实参给出的那个变量所表示的存储位置。(即引用参数直接指向传进来的实际参数所指向的内存地址。)
当形参为引用形参时,方法调用中的对应实参必须由关键字ref并后接一个与形参类型相同的variable-reference组成。变量在可以作为引用形参传递之前,必须先明确赋值。
在方法内部,引用形参始终被认为是明确赋值的。
声明为迭代器的方法不能有引用形参。
下面的示例:
using System;
class Test
{
static void Swap(ref int x, ref int y){
int temp = x;
x = y;
y = temp;
}
static void Main(){
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine(“i = {0}, j = {1}”, i, j);
}
}
产生输出:
i = 2, j = 1
[1]值类型的引用参数:
注意:引用参数并不创建变量的副本;
使用ref修饰符显式指出——此方法的副作用是改变实际参数的值,也就是利用此副作用;
方法内的参数获得新值时,方法外部的变量也会获得新值;
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
int y = 1;
IWantSideEffect(ref y);
Console.WriteLine(y);
}
static void IWantSideEffect(ref int x) //值类型引用参数所指向的内存地址,就是main方法里的变量y所指向的地址
{
x = x + 100; //变量通过IWantSideEffect方法来获得值的时候,变量里的值也会改变
}
}
}
[2]引用类型的引用参数:创建新对象
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student outterStu = new Student() { Name = “Tim” }; //使用outterStu来与stu进行区分,并设置不同的名字Tim
Console.WriteLine(“HashCode={0},Name={1}”, outterStu.GetHashCode(), outterStu.Name); //打印出调用方法前的GetHashCode和Name属性的值
Console.WriteLine("——————————————————————————");
IWantSideEffect(ref outterStu); //调用方法,引用类型需要加上ref关键字
Console.WriteLine(“HashCode={0},Name={1}”, outterStu.GetHashCode(), outterStu.Name); //打印调用后的值,发现参数和方法外的变量所引用的对象是一个对象,且在方法体外定义的对象
}
static void IWantSideEffect(ref Student stu) //stu是引用类型的类型参数,引用的是Student类
{
stu = new Student() { Name = "Tom" }; //在方法体中为引用类型赋上新值
Console.WriteLine("HashCode={0},Name={1}",stu.GetHashCode(),stu.Name); //打印出来
}
}
class Student
{
public string Name { get; set; }
}
}
[3]引用类型的引用参数:不创建新对象只改变对象值
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student outterStu = new Student() { Name = “Tim” }; //调用方法
Console.WriteLine(“HashCode={0},Name={1}”, outterStu.GetHashCode(), outterStu.Name); //打印方法外的变量的GetHashCode和Name值
Console.WriteLine("————————————————————");
SomeSideEffect(ref outterStu); //调用方法,将outterStu传进去
Console.WriteLine(“HashCode={0},Name={1}”, outterStu.GetHashCode(), outterStu.Name); //打印调用后的两个值
//结果发现,三个输出的GetHashCode和Name值是一样的,对象里的属性值发生了改变Tim-》Tom,GetHashCode没有改变,所以我们一直在操作一个对象
}
static void SomeSideEffect(ref Student stu)
{
stu.Name = "Tom"; //不为引用参数赋新值,而是只通过这个参数访问由它引用的对象
Console.WriteLine("HashCode={0},Name={1}", stu.GetHashCode(), stu.Name); //打印对象的GetHashCode和Name属性值
}
}
class Student
{
public string Name { get; set; }
}
}
与值参数一样的结果,但是值参数内存操作机理不同,值参数的传值函数在内存中创建了实际参数的副本,即stu参数和outterStu变量所指向的内存地址是不一样的,但是这两个不同的地址却存储着一个相同的地址,这个地址是实例在堆内存当中的地址,这是传值的情况。引用参数的情况是这样的,stu参数和outterStu变量存储的地址是同一个地址,储存的就是对象在堆内存当中的地址。
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
Student outterStu = new Student() { Name = “Tim” }; //调用方法
Console.WriteLine(“HashCode={0},Name={1}”, outterStu.GetHashCode(), outterStu.Name); //打印方法外的变量的GetHashCode和Name值
Console.WriteLine("————————————————————");
SomeSideEffect(outterStu); //调用方法,将outterStu传进去
Console.WriteLine(“HashCode={0},Name={1}”, outterStu.GetHashCode(), outterStu.Name); //打印调用后的两个值
//结果发现,三个输出的GetHashCode和Name值是一样的,对象里的属性值发生了改变Tim-》Tom,GetHashCode没有改变,所以我们一直在操作一个对象
}
static void SomeSideEffect(Student stu)
{
stu.Name = "Tom"; //不为引用参数赋新值,而是只通过这个参数访问由它引用的对象
Console.WriteLine("HashCode={0},Name={1}", stu.GetHashCode(), stu.Name); //打印对象的GetHashCode和Name属性值
}
}
class Student
{
public string Name { get; set; }
}
}
4、数组参数
必须是形参列表中的最后一个,由params修饰;
举例:String.Format方法和String.Sqlit方法。
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
//int[] myIntArray = new int[] { 1, 2, 3 }; //声明一个数组,如果使用了params就不用自己声明数组了
int result = CalculateSun(1, 2, 3); //如果使用params,在CalculateSun(1,2,3)中加上数组就好,系统会自动为我们创建数组
Console.WriteLine(result);
}
static int CalculateSun(params int[] intArray) //创建一个方法:传进来一个整型数组,然后将数组里面所有元素的和返回出去
{
int sum = 0;
foreach (var item in intArray)
{
sum += item;
}
return sum;
}
}
}
String.Sqlit方法:
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
string str = “Tim;Tom,Arry.Lisa”; //数组中含有特殊间隔符
string[] result = str.Split(’;’, ‘,’, ‘.’); //Split分割数组
foreach (var name in result) //迭代一下这个数组
{
Console.WriteLine(name);
}
}
}
}
5、具名参数
参数的位置不再受约束
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
PrintInfo(age: 23,name: “Tom”); //参数的位置不受约束,并且可读性很高
}
static void PrintInfo(string name, int age)
{
Console.WriteLine("Hello {0},you are {1}.",name,age);
}
}
}
6、可选参数
参数因为具有默认值而变得“可选”
不推荐使用可选参数
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
PrintInfo(); //调用方法,里面没有写参数
}
static void PrintInfo(string name = "Tim", int age = 24) //声明方法时参数是带有默认值的,如果在调用时没写参数,则这个参数自动获得声明时的默认值
{
Console.WriteLine("Hello {0},you are {1}.", name, age);
}
}
}
7、扩展方法(this参数)
方法必须是共有、静态的,即被public static所修饰
必须是形参列表中的第一个,由this修饰
必须由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法
举例:LINQ方法
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
//当我们无法对一个类型的源码进行修改时,可以使用扩展方法,为目标数据类型来追加方法
double x = 3.14159;
double y = x.Round(4); //不能直接使用Round方法,扩展一个方法来使用
Console.WriteLine(y);
}
}
static class DoubleExtension //声明静态类
{
public static double Round(this double input, int digits) //this修饰符,生成扩展方法
{
double result = Math.Round(input, digits);
return result;
}
}
}
举例:LINQ方法
namespace ParametersExample
{
class Program
{
static void Main(string[] args)
{
List myList = new List() { 11, 12, 13, 14, 15 };
bool result = myList.All(i => i > 10); //ALL是扩展方法
Console.WriteLine(result);
}
static bool AllGreaterThanTen(List intList)
{
foreach (var item in intList)
{
if (item<=10)
{
return false;
}
}
return true;
}
}
}
8、总结:各种参数的使用场景总结:
[1]传值参数:参数的默认传递方式(参数是实参的副本,不会影响实参的值)
[2]输出参数:用于除返回值外还需要输出的场景
[3]引用参数:用于需要修改实际参数值的场景
[4]数组参数:用于简化方法的调用
[5]具名参数:提高可读性
[6]可选参数:参数拥有默认值
[7]扩展方法(this参数):为目标数据类型“追加”方法
十、委托
1、什么是委托
[1]委托(delegate)是函数指针的“升级版”
实例:C/C++中的函数指针
[2]一切皆地址
变量(数据)是以某个地址为起点的一段内存中所存储的值
函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
[3]直接调用与间接调用
直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行->返回
间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行->返回
[4]Java中没有与委托相对应的功能实体
Java没有指针,不允许直接访问地址[5]委托的简单使用
[5]委托的简单实用:(下面两个委托是C#准备好的)
Action委托(没有返回值)
Func委托(有返回值)
namespace DelegateExample
{
class Program
{
static void Main(string[] args)
{
//委托是函数指针的升级版:可以按照一定的约束,指向某些目标方法,帮助我们对方法的间接调用
//第一种委托:action
Calculator calculator = new Calculator();
Action action = new Action(calculator.Report); //calculator.Report后面不加圆括号,只需要方法名就好(如果加上就是调用了)
calculator.Report(); //直接调用
action.Invoke(); //间接调用
action(); //类似于指针的方式
//第二种委托:function委托。泛型委托,提供目标方法的参数类型(17种重载)
Func func1 = new Func(calculator.Add); //
Func func2 = new Func(calculator.Sub); //<>中的前两个int是目标方法的参数类型,第三个是返回值类型
int x = 100;
int y = 200;
int z = 0;
z = func1(x, y);
Console.WriteLine(z);
z = func2.Invoke(x, y);
Console.WriteLine(z);
}
}
class Calculator
{
public void Report()
{
Console.WriteLine("I have 3 methods.");
}
public int Add(int a, int b)
{
int result = a + b;
return result;
}
public int Sub(int a, int b)
{
int result = a - b;
return result;
}
}
}
double Add(double x, double y) { return x + y; }
double Sub(double x, double y) { return x - y; }
double Mul(double x, double y) { return x * y; }
double Div(double x, double y) { return x / y; }
返回值的数据类型一致
参数列表在个数和数据类型上一致(参数名不需要一样)
namespace DelegateExample
{
//自定义委托类型
public delegate double Calc(double x, double y); //声明委托。delegate后面的double是返回值的类型。括号里加上目标方法的参数列表
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Calc calc1 = new Calc(calculator.Add); //委托的实例,委托的构造函数要求在委托里面加上方法,添加方法的参数要与委托一致
Calc calc2 = new Calc(calculator.Sub);
Calc calc3 = new Calc(calculator.Mul);
Calc calc4 = new Calc(calculator.Div);
double a = 100;
double b = 200;
double c = 0;
c = calc1(a, b); //间接调用委托,类似指针的调用方法
Console.WriteLine(c);
c = calc2(a, b); //间接调用委托,类似指针的调用方法
Console.WriteLine(c);
c = calc3.Invoke(a, b); //间接调用委托
Console.WriteLine(c);
c = calc4.Invoke(a, b); //间接调用委托
Console.WriteLine(c);
}
}
class Calculator
{
public double Add(double x, double y)
{
return x + y;
}
public double Sub(double x, double y)
{
return x - y;
}
public double Mul(double x, double y)
{
return x * y;
}
public double Div(double x, double y)
{
return x / y;
}
}
}
3、委托的使用
[1]委托的一般使用:
实例:把方法当作参数传给另一个方法
正确使用1:模板方法,“借用”指定的外部方法来产生结果
相当于“填空题”
常位于代码中部
委托有返回值
正确使用2:回调(callback)方法,调用指定的外部方法
相当于“流水线”
常位于代码末尾
委托无返回值
【*无论是模板方法还是回调方法,都是用委托类型的参数来封装一个外部的方法,然后将这个方法传进方法的内部来进 行间接调用】
模板方法:
namespace DelegateExample
{
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory(); //调用工厂实例
WrapFactory wrapFactory = new WrapFactory(); //包装工厂的实例
Func func1 = new Func(productFactory.MakePizza); //第一个委托实例,返回值为Product,参数为空。用它封装产品工厂里生产披萨饼的那个方法
Func func2 = new Func(productFactory.MakeToyCar); //func2封装MakeToyCar方法
//正式调用模板方法
Box box1 = wrapFactory.WrapProduct(func1); //用Box类型的变量box1接收返回值。func1作为参数,代进了WrapProduct方法,将结果赋给box1
Box box2 = wrapFactory.WrapProduct(func2);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
class Product //产品类
{
public string Name { get; set; } //每个产品都有自己的名字
}
class Box //包装箱类
{
public Product Product { get; set; } //每个包装箱里都有一个product属性,就是所包装的产品
}
class WrapFactory //把产品包装上盒子给用户
{
//为了方法的复用,将参数设为委托,传入一个方法,那所有的元素都能进行这个操作了
public Box WrapProduct(Func getProduct) //模板方法,接收一个委托类型的参数(Function委托)。所封装的方法能返回Product类型的对象
{
//模板方法的逻辑:
Box box = new Box(); //Box实例
Product product = getProduct.Invoke(); //调用完后获得一个Product类型的对象(拿到Product)
box.Product = product; //将box的Product属性的值,设为刚刚拿到的product(将Product包装到Box里)
return box;
}
}
class ProductFactory //生产产品的工厂
{
public Product MakePizza() //Pizzar工厂,返回值为Product类型,参数为空
{
Product product = new Product();
product.Name = "Pizza"; //产品名
return product;
}
public Product MakeToyCar() //玩具汽车工厂
{
Product product = new Product();
product.Name = "ToyCar";
return product;
}
}
}
回调方法:
namespace DelegateExample
{
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory(); //调用工厂实例
WrapFactory wrapFactory = new WrapFactory(); //包装工厂的实例
Func func1 = new Func(productFactory.MakePizza); //第一个委托实例,返回值为Product,参数为空。用它封装产品工厂里生产披萨饼的那个方法
Func func2 = new Func(productFactory.MakeToyCar); //func2封装MakeToyCar方法
Logger logger = new Logger(); //声明实例
Action log = new Action(logger.Log); //委托实例
//正式调用模板方法
Box box1 = wrapFactory.WrapProduct(func1,log); //用Box类型的变量box1接收返回值。func1作为参数,代进了WrapProduct方法,将结果赋给box1
Box box2 = wrapFactory.WrapProduct(func2,log);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
class Logger //记录程序运行状态
{
public void Log(Product product) //没有返回值,只做记录
{
Console.WriteLine("Product '{0}' create at {1}.Price is {2}.", product.Name, DateTime.UtcNow, product.Price); //DateTime.UtcNow表示没有时区的时间
}
}
class Product //产品类
{
public string Name { get; set; } //每个产品都有自己的名字
public double Price { get; set; }
}
class Box //包装箱类
{
public Product Product { get; set; } //每个包装箱里都有一个product属性,就是所包装的产品
}
class WrapFactory //把产品包装上盒子给用户
{
//为了方法的复用,将参数设为委托,传入一个方法,那所有的元素都能进行这个操作了
public Box WrapProduct(Func getProduct,Action logCallba) //模板方法,接收一个委托类型的参数(Function委托)。所封装的方法能返回Product类型的对象
{
//模板方法的逻辑:
Box box = new Box(); //Box实例
Product product = getProduct.Invoke(); //调用完后获得一个Product类型的对象(拿到Product)
if (product.Price>=50) //价格超过50就log一下
{
logCallba(product);
}
box.Product = product; //将box的Product属性的值,设为刚刚拿到的product(将Product包装到Box里)
return box;
}
}
class ProductFactory //生产产品的工厂
{
public Product MakePizza() //Pizzar工厂,返回值为Product类型,参数为空
{
Product product = new Product();
product.Name = "Pizza"; //产品名
product.Price = 12;
return product;
}
public Product MakeToyCar() //玩具汽车工厂
{
Product product = new Product();
product.Name = "ToyCar";
product.Price = 200;
return product;
}
}
}
注意:难精通+易使用+功能强大东西,一旦被滥用则后果非常严重:
缺点1:这是一种方法级别的紧耦合(违反设计模式),现实工作中一定要慎之又慎
缺点2:使用不当的话,使可读性下降、debug的难度增加
缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
缺点4:委托使用不当有可能造成内存泄漏和程序性能下降
[2]委托的高级使用:
【1】多播(multicast)委托:一个委托里封装着不止一个方法。
namespace MulticastDelegateExample
{
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };
//创建委托
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
//调用委托,单播委托
//action1.Invoke();
//action2.Invoke();
//action3.Invoke();
//多播委托(action1封装有顺序)
action1 += action2; //将action2合并到action1里
action1 += action3; //将action3合并到action1里
action1.Invoke(); //action1里封装了三个方法
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework() //实例方法
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).",this.ID,i);
Thread.Sleep(1000); //1000毫秒==1秒
}
}
}
}
隐式异步调用:
同步与异步的简介:
中英文的语言差异;
同步:你做完了我(在你的基础上)接着做;
异步:咱们两个同时做(相当于汉语中的“同步进行”)。
同步调用与异步调用:
每一个运行的程序是一个进程(process);
每个进程可以有一个或多个线程(thread);
同步调用是在同一线程内;
异步调用的底层机理是多线程;
串行同步单线程,并行异步多线程。
隐式多线程V.S. 显式多线程:
直接同步调用:使用方法名;
间接同步调用:使用单播/多播委托的Invoke方法;
隐式异步调用:使用委托的BeginInvoke;
显示异步调用:使用Thread或Task。
/同步代码/
namespace MulticastDelegateExample
{
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };
///*直接同步调用:用方法名字直接调用方法*/
//stu1.DoHomework(); //直接调用三个方法,前一个执行完,再执行后一个方法
//stu2.DoHomework();
//stu3.DoHomework();
/*第二种:单播委托间接、同步调用*/
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
//action1.Invoke();
//action2.Invoke();
//action3.Invoke();
/*第三种:多播委托的间接、同步调用*/
action1 += action2;
action1 += action3;
action1.Invoke();
for (int i = 0; i < 10; i++) //调用完方法后,主线程还有事情要做
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread {0}.",i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework() //实例方法
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000); //1000毫秒==1秒
}
}
}
}
/使用委托进行隐式的异步调用代码示例:/
namespace MulticastDelegateExample
{
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
//使用委托进行隐式异步调用(使用BeginInvoke方法进行隐式异步调用)
//主线程和三个分支线程会一起执行,谁也不会等着谁。因此,有可能会在争抢资源时发生冲突。
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
for (int i = 0; i < 10; i++) //调用完方法后,主线程
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread {0}.",i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework() //实例方法
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000); //1000毫秒==1秒
}
}
}
}
/显式的异步调用(即我们自己声明多线程)/
namespace MulticastDelegateExample
{
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };
//显式的异步调用:
///*第一种:(古老)使用Thread创建三个线程*/
//Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
//Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
//Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
启动线程:
//thread1.Start();
//thread2.Start();
//thread3.Start();
/*第二种:使用Task进行显式异步调用*/
Task task1 = new Task(new Action(stu1.DoHomework));
Task task2 = new Task(new Action(stu2.DoHomework));
Task task3 = new Task(new Action(stu3.DoHomework));
//启动线程:
task1.Start();
task2.Start();
task3.Start();
for (int i = 0; i < 10; i++) //调用完方法后,主线程
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread {0}.",i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework() //实例方法
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000); //1000毫秒==1秒
}
}
}
}
【2】应该适时地使用接口(interface)取代一些对委托的使用:
Java完全地使用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体。
namespace DelegateExample
{
class Program
{
static void Main(string[] args)
{
IProductFactory pizzaFactory = new PizzaFactory(); //调用工厂实例
IProductFactory toyCarFactory = new ToyCarFactory(); //包装工厂的实例
WrapFactory wrapFactory = new WrapFactory();
//正式调用模板方法
Box box1 = wrapFactory.WrapProduct(pizzaFactory); //用Box类型的变量box1接收返回值。func1作为参数,代进了WrapProduct方法,将结果赋给box1
Box box2 = wrapFactory.WrapProduct(toyCarFactory);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
interface IProductFactory //使用接口取代委托
{
Product Make(); //这个接口只有一个方法,接口的返回值类型是Product
}
class PizzaFactory : IProductFactory //披萨工厂类,实现IProductFactory接口
{
public Product Make()
{
Product product = new Product();
product.Name = "Pizza"; //产品名
product.Price = 12;
return product;
}
}
class ToyCarFactory : IProductFactory //玩具汽车工厂类,实现IProductFactory接口
{
public Product Make()
{
Product product = new Product();
product.Name = "ToyCar";
product.Price = 200;
return product;
}
}
class Product //产品类
{
public string Name { get; set; } //每个产品都有自己的名字
public double Price { get; set; }
}
class Box //包装箱类
{
public Product Product { get; set; } //每个包装箱里都有一个product属性,就是所包装的产品
}
class WrapFactory //把产品包装上盒子给用户
{
//为了方法的复用,将参数设为委托,传入一个方法,那所有的元素都能进行这个操作了
public Box WrapProduct(IProductFactory productFactory) //模板方法。需要工厂类型参数
{
//模板方法的逻辑:
Box box = new Box(); //Box实例
Product product = productFactory.Make(); //调用完后获得一个Product类型的对象(拿到Product)
box.Product = product; //将box的Product属性的值,设为刚刚拿到的product(将Product包装到Box里)
return box;
}
}
}
十一、事件
1、初步了解事件
【1】定义:单词Event,译为“事件”
《牛津词典》中的解释是“a thing that happens, especially something important”
通顺的解释就是“能够发生的什么事情”
【2】角色:使对象或类具备通知能力的成员
(中译)事件(event)是一种使对象或类能够提供通知的成员
(原文)An event is a menber that enables an object or class to provide notifications.
"对象O拥有一个事件E"想表达的思想是:当事件E发生的时候,O有能力通知别的对象
【3】使用:用于对象或类间的动作协调与信息传递(消息推送)
经由事件发送过来的与事件本身相关的数据,称为事件参数(EventArgs)。
处理事件时具体所做的事情,叫做事件处理器。
【4】原理:事件模型(event model)中的两个“5
“发生->响应”中的5个部分——闹钟响了你起床、孩子饿了你做饭……这里隐含着“订阅”关系
“发生->响应”中的5个部分——[1]我有一个事件 -> [2]一个人或者一群人关心我的这个事件 -> [3]我的这个事 件发生了 -> [4]关心这个事件的人会被一次通知到 -> [5]被通知到的人根据拿到的事件信息(又称“事件数据”、 “事件参数”、“通知”)对事件进行响应(又被“处理事件”)。
事件的功能 = 通知 + 可选的事件参数(即详细信息)
【5】提示:
[1]事件多用于桌面、手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的
[2]各种编程语言对这个机制的实现方法不尽相同
[3]Java语言里没有事件这种成员,也没有委托这种数据类型。Java的“事件”是使用接口来实现的
[4]MVC、MVP、MVVM等模式,是事件模式更高级、更有效的“玩法”
[5]日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少,所以先学使用
2、事件的应用
【1】实例演示
派生(继承)与扩展(extends)
【2】事件模型的五个组成部分:
[1]事件的拥有者(event source,对象/类):事件的主体、事件消息的发送者都统称为事件的拥有者(方法里 的参数叫做Sender)。事件不会主动通知。在事件的拥有者完成某个逻辑之后,明确的告诉事件成员需要通知, 事件才会使用通知功能。就如Click按钮,按下时才会发生。(即,事件不会主动发生,只有当事件拥有者的内 部某些逻辑被触发后,它才能发生,能够发挥通知的作用。)
[2]事件成员(event,成员):事件自己不会主动发生的,。
[3]事件的响应者(event subscriber,对象):被通知到(即,订阅了事件)的类和对象。
[4]事件处理器(event handler,[方法]成员)——本质上是一个回调方法。C#规定:用于订阅事件的事件处理 器,必须和事件遵守同一个约定。这个约定既约束了事件能够把什么样的消息发送给事件的处理器,也约束了事 件处理器能够处理什么样的约定。
[5]事件订阅——把事件处理器与事件关联在一起,本质上是一种以委托类型为”基础“的约定。实现了如下三点:
第一,当事件发生时,都通知到了谁,即那些订阅了事件的对象。
第二,拿什么样的方法(事件处理器)才能够处理这些事件。
第三,事件的响应者具体拿哪个方法来处理事件。
【3】注意:
-事件处理器是方法成员。
-挂接事件处理器时,可以使用委托实例,也可以直接使用方法名,这是个”语法糖“。
-事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测。
-事件可以同步调用也可以异步调用。
-一个事件可以挂接多个事件处理器,一个事件处理器可以被多个事件所挂接。
【4】组合方式:(*)[1]标准的事件机制模型:是MVC、MVP等设计模式的雏形
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
//一个简单的事件
Timer timer = new Timer(); //Timer是事件的拥有者
timer.Interval = 1000; //设置时间间隔
Boy boy = new Boy(); //事件的响应者
Girl girl = new Girl();
timer.Elapsed += boy.Action; //Elapsed是事件。+=:事件订阅符。(敲完Action后,双击提醒框,自动生成事件处理器)
timer.Elapsed += girl.Action; //Action是事件名,最好有意义
timer.Start(); //时间开始
Console.ReadLine();
}
}
class Boy
{
//事件的处理器
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump!"); //每触发一次,跳一次舞
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Sing!");
}
}
}
(***)[2]事件的拥有者是事件响应者的一个字段成员:使用最普遍。因为这个机制模型是Windows平 台上默认的事件订阅和处理结构。
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm();
form.ShowDialog();
}
}
class MyForm : Form //4、MyForm:事件的响应者
{
private TextBox textBook; //文本框成员
private Button button; //按钮成员。1、button:事件的拥有者。button是成员
public MyForm() //构造函数。
{
this.textBook = new TextBox();
this.button = new Button();
this.Controls.Add(this.button);
this.Controls.Add(this.textBook);
this.button.Click += this.ButtonClicked; //2、.Click:事件。5、订阅
this.button.Text = "Say:";
this.button.Top = 50; //button高度
}
private void ButtonClicked(object sender, EventArgs e) //3、ButtonClicked:事件处理器
{
this.textBook.Text = "Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!Hello!";
}
}
}
/窗口应用代码:两种挂接的方式/
namespace WinFormExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.button3.Click += new EventHandler(this.ButtonClicked); //第二种挂接的方式
this.button4.Click += delegate(object sender, EventArgs e) //第三种挂接方式(已经弃用)。匿名委托(没有方法名,不能被调用)
{
this.textBox1.Text = "Hello4!";
};
//Lambda表达式。编译器甚至能通过委托约束来推断出这两个参数是什么数据类型的(即,可以不写参数类型就可以运行)。
this.button5.Click += (object sender, EventArgs e) => //第四种挂接方式(比较流行)。
{
this.textBox1.Text = "Hello5";
};
}
private void ButtonClicked(object sender, EventArgs e) //第一种挂接的方式
{
if (sender == this.button1)
{
this.textBox1.Text = "Hello1!";
}
if (sender == this.button2)
{
this.textBox1.Text = "Hello2!";
}
if (sender == this.button3)
{
this.textBox1.Text = "Hello3";
}
}
}
}
(无)[3]
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Form form = new Form(); //form就是事件的拥有者
Controller controller = new Controller(form); //事件的响应者controller
form.ShowDialog(); //显示窗口
}
}
class Controller
{
private Form form; //Form类型的字段
public Controller(Form form) //构造器
{
if (form != null) //判断是否为空。如果一个对象是空的话,是没有办法访问事件的。
{
this.form = form; //this.form里的form是字段,后面的form是传进来的参数。
//form字段获得对象form的引用之后,挂接(+=)一个事件处理器,用Controller类里的实例(this.)作为事件处理器,名字是FormClicked。Click事件。
this.form.Click += this.FormClicked; //事件定义
}
}
//参数中EventArgs约束会有所不同。也就是说,不能拿影响Elapsed事件处理器去响应Click事件——因为循环约束不同,所以他们是不通的。
private void FormClicked(object sender, EventArgs e) //事件处理器的方法。
{
this.form.Text = DateTime.Now.ToString(); //Text显示当前时间
}
}
}
(**)[4]这个方法用自己的方法订阅着自己的事件:
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm(); //4、这个实例:事件的响应者。
form.Click += form.FormClickded; //2、.Click:事件。5、+=:事件订阅
form.ShowDialog();
}
}
class MyForm : Form //新建类MyForm,派生自Form(继承)。1、MyForm:事件拥有者。
{
internal void FormClickded(object sender, EventArgs e) //3、FormClickded:事件处理器
{
this.Text = DateTime.Now.ToString();
}
}
}
3、深入理解事件
4、事件的声明
【1】事件的声明:
完整声明:
*创建事件的步骤:
*1、创建事件拥有者。
*2、创建委托类型OrderEventHandler。两个参数,第一个参数是事件拥有者。
*3、创建事件消息数据类型OrderEventAgs,作为第二步的第二个参数。
*4、在事件拥有者中创建私有字段orderEventHandler。
*5、在拥有者中创建事件Order。Order包含add(添加事件处理器)和remove(移除事件处理器)
*6、创建事件响应者Waiter。
*7、订阅+=。把事件和事件响应者连接到一起。
*8、创建事件响应者Action。
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); //事件的拥有者
Waiter waiter = new Waiter(); //事件的响应者
customer.Order += waiter.Action; //使用一个叫做Action的方法作为事件的事件处理器。(Order事件;+=订阅事件;Action事件处理器)
customer.Action(); //顾客进来坐下思考
customer.PayTheBill(); //付账
}
}
/*
OrderEventArgs、OrderEventHandler、Customer这3个类要配合在一起使用,访问级别要相同,都使用public
*/
public class OrderEventArgs : EventArgs //事件的名字+后缀(传递事件信息EventArgs):传递事件消息。EventArgs是系统提供的基类
{
public string DishName { get; set; } //菜名
public string Size { get; set; } //大小份
}
//使用EventHandler有三个意义:1、使用这个名字,表明这个委托类型是专门使用事件的,别人就不会拿这个委托做其他事情
//2、表明是约束事件处理器的;3、表明这个委托未来创建出来的实例是专门存储事件处理器的。(这3点增强可读性)
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //创建委托,为声明事件做准备。不能放进类里。
public class Customer //事件的拥有者
{
private OrderEventHandler orderEventHandler; //用委托类型声明一个字段,并不被外界访问到。用于引用事件处理器
public event OrderEventHandler Order //点菜事件。声明事件,并希望被外界访问到。用OrderEventHandler约束事件。(事件)
{
//事件处理器的添加器
add
{
this.orderEventHandler += value; //接收从外界传进来的EventHandler。value是上下文关键字,表示赋进来的值。
}
//移除器
remove
{
this.orderEventHandler -= value;
}
}
public double Bill { get; set; } //账单
public void PayTheBill() //付账
{
Console.WriteLine("I will ${0}.", this.Bill);
}
public void WalkIn() //走进
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown() //坐下
{
Console.WriteLine("Sit down.");
}
public void Think() //思考
{
for (int i = 0; i < 5; i++) //5秒
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.orderEventHandler != null) //(触发事件)为避免异常,判断引用存储事件处理器的委托是否为空。不为空就有人调用。
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this,e); //this表示自己点菜,e是菜品名字和大小份
}
}
public void Action() //一连串调用动作。
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter //事件的拥有者
{
public void Action(Customer customer, OrderEventArgs e) //事件处理器,记账
{
Console.WriteLine("I will serve you the dish - {0}.",e.DishName); //菜品名(第二个参数传进来的值)
double price = 10; //假设每份菜品10元
switch (e.Size)
{
case "small": //如果是小份,就价格减半
price = price * 0.5;
break;
case "large": //如果是大份,价格加一半
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price; //将所点菜品价格相加,生成账单。(第一个参数传进来的)
}
}
}
简略声明(字段式声明,field-like):委托类型的字段依旧在,只是隐藏起来了。
注意,在Customer类内部能够使用Order事件去做非空比较以及调用Order.Invoke方法纯属不得已而为之,因 为使用事件的简化声明时,我们没有手动声明一个委托类型的字段。这是微软编译器语法糖所造成的语法冲突和 前后不一致。
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); //事件的拥有者
Waiter waiter = new Waiter(); //事件的响应者
customer.Order += waiter.Action; //使用一个叫做Action的方法作为事件的事件处理器。(Order事件;+=订阅事件;Action事件处理器)
customer.Action(); //顾客进来坐下思考
customer.PayTheBill(); //付账
}
}
/*
OrderEventArgs、OrderEventHandler、Customer这3个类要配合在一起使用,访问级别要相同,都使用public
*/
public class OrderEventArgs : EventArgs //事件的名字+后缀(传递事件信息EventArgs):传递事件消息。EventArgs是系统提供的基类
{
public string DishName { get; set; } //菜名
public string Size { get; set; } //大小份
}
//使用EventHandler有三个意义:1、使用这个名字,表明这个委托类型是专门使用事件的,别人就不会拿这个委托做其他事情
//2、表明是约束事件处理器的;3、表明这个委托未来创建出来的实例是专门存储事件处理器的。(这3点增强可读性)
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //创建委托,为声明事件做准备。不能放进类里。
public class Customer //事件的拥有者
{
public event OrderEventHandler Order; //声明事件。OrderEventHandler约束;Order事件名。(不是字段,和字段很像)
public double Bill { get; set; } //账单
public void PayTheBill() //付账
{
Console.WriteLine("I will ${0}.", this.Bill);
}
public void WalkIn() //走进
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown() //坐下
{
Console.WriteLine("Sit down.");
}
public void Think() //思考
{
for (int i = 0; i < 5; i++) //5秒
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.Order != null) //(触发事件)为避免异常,判断引用存储事件处理器的委托是否为空。不为空就有人调用。orderEventHandler-》Order
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e); //this表示自己点菜,e是菜品名字和大小份。orderEventHandler-》Order
}
}
public void Action() //一连串调用动作。
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter //事件的拥有者
{
public void Action(Customer customer, OrderEventArgs e) //事件处理器,记账
{
Console.WriteLine("I will serve you the dish - {0}.",e.DishName); //菜品名(第二个参数传进来的值)
double price = 10; //假设每份菜品10元
switch (e.Size)
{
case "small": //如果是小份,就价格减半
price = price * 0.5;
break;
case "large": //如果是大份,价格加一半
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price; //将所点菜品价格相加,生成账单。(第一个参数传进来的)
}
}
}
【2】有了委托字段/属性,为什么还需要事件?
事件只能在“+=”或“-=”的左边,如果是字段,就会被滥用。
为了程序的逻辑更加”有道理“、更加安全,谨防”借刀杀人“。例子如下:
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
//customer.Action();
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Quanxi";
e.Size = "large";
OrderEventArgs e2 = new OrderEventArgs();
e2.DishName = "Beer";
e2.Size = "large";
Customer badGuy=new Customer();
badGuy.Order += waiter.Action;
badGuy.Order.Invoke(customer, e);
badGuy.Order.Invoke(customer, e2);
customer.PayTheBill();
}
}
public class OrderEventArgs : EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class Customer
{
public OrderEventHandler Order;
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("I will pay the bill ${0}.", this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk in the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Tinking...");
Thread.Sleep(1000);
}
if (this.Order != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao";
e.Size = "large";
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve the dish-{0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price *= 0.5;
break;
case "large":
price *= 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
【3】所以事件的本质是委托字段的一个包装器:
这个包装器对委托字段的访问起限制作用,相当于一个”蒙板“;
封装(encapsulation)的一个重要功能就是隐藏;
事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能;
添加/移除事件处理器的时候可以 直接使用方法名,这是委托实例所不具的功能。
【4】用于声明事件的委托类型的命名约定:
[1]用于声明Foo事件的委托,一般命名为FoolEventHandler(除非是一个非常通用的事件约束)。
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); //事件的拥有者
Waiter waiter = new Waiter(); //事件的响应者
customer.Order += waiter.Action; //使用一个叫做Action的方法作为事件的事件处理器。(Order事件;+=订阅事件;Action事件处理器)
customer.Action(); //顾客进来坐下思考
customer.PayTheBill(); //付账
}
}
/*
OrderEventArgs、OrderEventHandler、Customer这3个类要配合在一起使用,访问级别要相同,都使用public
*/
public class OrderEventArgs : EventArgs //事件的名字+后缀(传递事件信息EventArgs):传递事件消息。EventArgs是系统提供的基类
{
public string DishName { get; set; } //菜名
public string Size { get; set; } //大小份
}
//使用EventHandler有三个意义:1、使用这个名字,表明这个委托类型是专门使用事件的,别人就不会拿这个委托做其他事情
//2、表明是约束事件处理器的;3、表明这个委托未来创建出来的实例是专门存储事件处理器的。(这3点增强可读性)
//public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //创建委托,为声明事件做准备。不能放进类里。
public class Customer //事件的拥有者
{
//event:声明事件的关键字,不加就会变成字段定义。事件的作用是防止字段被滥用,增加安全性。
//EventHandler委托:厂商为我们准备好的一个通用的专门用于去声明事件的委托类型。如果使用EventHandler,就不用声明委托了。
public event EventHandler Order; //声明事件。OrderEventHandler约束;Order事件名。(不是字段,和字段很像)。
public double Bill { get; set; } //账单
public void PayTheBill() //付账
{
Console.WriteLine("I will ${0}.", this.Bill);
}
public void WalkIn() //走进
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown() //坐下
{
Console.WriteLine("Sit down.");
}
public void Think() //思考
{
for (int i = 0; i < 5; i++) //5秒
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.Order != null) //(触发事件)为避免异常,判断引用存储事件处理器的委托是否为空。不为空就有人调用。orderEventHandler-》Order
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e); //this表示自己点菜,e是菜品名字和大小份。orderEventHandler-》Order
}
}
public void Action() //一连串调用动作。
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter //事件的拥有者
{
public void Action(object sender, EventArgs e) //事件处理器,记账
{
Customer customer = sender as Customer; //做类型转换
OrderEventArgs orderInfo = e as OrderEventArgs;
Console.WriteLine("I will serve you the dish - {0}.",orderInfo.DishName); //菜品名(第二个参数传进来的值)
double price = 10; //假设每份菜品10元
switch (orderInfo.Size)
{
case "small": //如果是小份,就价格减半
price = price * 0.5;
break;
case "large": //如果是大份,价格加一半
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price; //将所点菜品价格相加,生成账单。(第一个参数传进来的)
}
}
}
[2]FoolEventHandler委托的参数一般有两个(由Win32 API演化而来,历史悠久):
第一个参数是object类型,名字为sender,实际上就是事件的拥有者、事件的source。
第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e。也就是前面讲过的事件参数。
虽然没有官方的说法,但是我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息”。
[3]触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”:
访问级别为protected,不能为public,不然又成了可以“借刀杀人”了。【protected:被自己的成员访问】
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); //事件的拥有者
Waiter waiter = new Waiter(); //事件的响应者
customer.Order += waiter.Action; //使用一个叫做Action的方法作为事件的事件处理器。(Order事件;+=订阅事件;Action事件处理器)
customer.Action(); //顾客进来坐下思考
customer.PayTheBill(); //付账
}
}
/*
OrderEventArgs、OrderEventHandler、Customer这3个类要配合在一起使用,访问级别要相同,都使用public
*/
public class OrderEventArgs : EventArgs //事件的名字+后缀(传递事件信息EventArgs):传递事件消息。EventArgs是系统提供的基类
{
public string DishName { get; set; } //菜名
public string Size { get; set; } //大小份
}
//使用EventHandler有三个意义:1、使用这个名字,表明这个委托类型是专门使用事件的,别人就不会拿这个委托做其他事情
//2、表明是约束事件处理器的;3、表明这个委托未来创建出来的实例是专门存储事件处理器的。(这3点增强可读性)
//public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //创建委托,为声明事件做准备。不能放进类里。
public class Customer //事件的拥有者
{
//event:声明事件的关键字,不加就会变成字段定义。事件的作用是防止字段被滥用,增加安全性。
//EventHandler委托:厂商为我们准备好的一个通用的专门用于去声明事件的委托类型。如果使用EventHandler,就不用声明委托了。
public event EventHandler Order; //声明事件。OrderEventHandler约束;Order事件名。(不是字段,和字段很像)。
public double Bill { get; set; } //账单
public void PayTheBill() //付账
{
Console.WriteLine("I will ${0}.", this.Bill);
}
public void WalkIn() //走进
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown() //坐下
{
Console.WriteLine("Sit down.");
}
public void Think() //思考
{
for (int i = 0; i < 5; i++) //5秒
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
this.OnOrder("Kongpao Chicken", "large");
}
protected void OnOrder(string dishName, string size) //触发Order事件的方法
{
if (this.Order != null) //(触发事件)为避免异常,判断引用存储事件处理器的委托是否为空。不为空就有人调用。orderEventHandler-》Order
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = dishName;
e.Size = size;
this.Order.Invoke(this, e); //this表示自己点菜(即事件的拥有者),e是菜品名字和大小份。orderEventHandler-》Order
}
}
public void Action() //一连串调用动作。
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter //事件的拥有者
{
public void Action(object sender, EventArgs e) //事件处理器,记账
{
Customer customer = sender as Customer; //做类型转换
OrderEventArgs orderInfo = e as OrderEventArgs;
Console.WriteLine("I will serve you the dish - {0}.",orderInfo.DishName); //菜品名(第二个参数传进来的值)
double price = 10; //假设每份菜品10元
switch (orderInfo.Size)
{
case "small": //如果是小份,就价格减半
price = price * 0.5;
break;
case "large": //如果是大份,价格加一半
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price; //将所点菜品价格相加,生成账单。(第一个参数传进来的)
}
}
}
【5】事件的命名约定:
带有时态的动词或者动词短语;
事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时。
【6】事件是基于委托的,有两层意思:(委托是底层基础,事件是上层建筑)
(1)事件需要用委托类型来做一个约束,这个约束既规定事件能够发送什么消息给事件的响应者,也规定了事件 响应者能收到什么样的事件消息。这就决定了事件响应者的事件处理器,必须能够和这个约束匹配上,它才能订 阅这个事件。
(2)当事件的响应者对事件的拥有者提供能够匹配这个事件处理器的事件之后,需要找个地方将事件处理器保存 记录起来,能够记录或者说引用方法的任务也只有委托类型的实例才能做到。
总结,事件这种成员,无论是从表层约束这层来讲,还是底层实现来讲,它都是依赖于委托类型的。
【7】事件与委托的关系
5、问题辨析
[1]事件真的是“以特殊方式声明的委托字段/实例”吗?
不是!只是声明的时候“看起来像”(对比委托字段与事件的简化声明,field-like)。
事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键 字则更像是一个修饰符——这就是错觉的来源之一。
订阅事件的时候+=操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看 起来像是一个委托字段——这是错觉的又一来源。
重申:事件的本质是加装在委托字段上的一个“蒙板”绝不是委托字段本身。
[2]为什么要使用委托类型来声明事件?
站在source的角度来看,是为了表明source能对外传递哪些消息。
站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件。
委托类型的实例将用于存储(引用)事件处理器。
[3]对比事件与属性:
属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用。
事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被滥用。
包装器永远都不可能是被包装的东西。
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
customer.Action();
customer.PayBill();
}
}
public class OrderEventArgs : EventArgs //创建数据类型,作为委托类型的第二个参数
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //第二个参数的名字一般写作“e”。委托类型:声明Order事件。
public class Customer
{
private OrderEventHandler orderEventHandler; //这个委托字段就是用来存储、引用那些事件处理器的
public event OrderEventHandler Order //事件。用OrderEventHandler约束事件
{
add //添加器
{
this.orderEventHandler += value; //用value存储传进来的EventHandler
}
remove //移除器
{
this.orderEventHandler -= value;
}
}
public double Bill { get; set; }
public void PayBill()
{
Console.WriteLine("I will pay ${0}.", this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDwon()
{
Console.WriteLine("Sit dwon.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.orderEventHandler != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDwon();
this.Think();
//this.PayBill();
}
}
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve you the dish - {0}.", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price *= 0.5;
break;
case "large":
price *= 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
十二、Linq
1、委托:
【1】什么是委托?
【2】委托类型是怎么声明出来的?
namespace Combine
{
class Program
{
static void Main(string[] args)
{
MyDele dele1 = new MyDele(M1); //dele1这个变量引用着一个MyDele类型的实例,这个实例里“包裹”着M1这个方法
//dele1 += (new Student()).SayHello; //dele1也包裹着Student.SayHello方法。简写,省略了实例的引用
//dele1(); //dele1.Invoke();省略而来。
}
static void M1()
{
Console.WriteLine("M1 is called.");
}
}
class Student
{
public void SayHello()
{
Console.WriteLine("Hello,I'm a student!");
}
}
delegate int MyDele(int a,int b); //创建委托
}
【3】泛型委托:
[1]自定义委托
namespace Combine
{
class Program
{
static void Main(string[] args)
{
MyDele deleAdd = new MyDele(Add); //int类型的委托
int result1 = deleAdd(100, 200);
Console.WriteLine(result1);
MyDele deleMul = new MyDele(Mul); //double类型的委托
double result2 = deleMul(3.0, 4.0);
Console.WriteLine(result2);
Console.WriteLine(deleAdd.GetType().IsClass); //判断deleAdd是否为类
}
static int Add(int a, int b)
{
return a + b;
}
static double Mul(double x, double y)
{
return x * y;
}
}
delegate T MyDele(T a, T b); //定义泛型委托
}
[2]Action委托:
namespace Combine
{
class Program
{
static void Main(string[] args)
{
Action
action(“LGQ”, 23);
}
static void SayHello(string name, int round) {
for (int i = 0; i < round; i++)
{
Console.WriteLine("Hello, " + name + " .");
}
}
static int Add(int a, int b)
{
return a + b;
}
static double Mul(double x, double y)
{
return x * y;
}
}
}
[3]Function委托:
namespace Combine
{
class Program
{
static void Main(string[] args)
{
var fun = new Func
double result = fun(3.0, 4.0);
Console.WriteLine(result);
}
static void SayHello(string name, int round) {
for (int i = 0; i < round; i++)
{
Console.WriteLine("Hello, " + name + " .");
}
}
static int Add(int a, int b)
{
return a + b;
}
static double Mul(double x, double y)
{
return x * y;
}
}
}
【4】我们必须自己创建委托类型吗?
【5】泛型委托的类型参数推断:
2、Lambda:
【1】方法与Lambda之间的关系:
Lambda表达式
作用:
1、匿名方法
2、Inline方法
namespace Combine
{
class Program
{
static void Main(string[] args)
{
//(int a, int b) => { return a + b; }:Lambda表达式
Func
int result = func(100, 200);
Console.WriteLine(result);
func = new Func((int a, int b) => { return a * b; });
result = func(3, 4);
Console.WriteLine(result);
func = new Func((a, b) => { return a * b; });
result = func(3, 4);
Console.WriteLine(result);
func = (a, b) => { return a + b; }; //最简约的Lambda表达式
result = func(100, 200);
Console.WriteLine(result);
}
}
}
【2】如何把一个Lambda表达式赋值给一个委托类型变量:
【3】如何把一个Lambda表达式“喂”给一个委托类型参数:
namespace Combine
{
class Program
{
static void Main(string[] args)
{
//DoSomeCalc((int a, int b) => { return a * b; },100,100);
//DoSomeCalc((a, b) => { return a * b; }, 100, 100);
DoSomeCalc((a, b) => { return a * b; }, 100, 100); //最简语句
}
//DoSomeCalc:泛型方法
//Func func:泛型的委托类型参数
//T x, T y:泛型参数
static void DoSomeCalc(Func func, T x, T y) { //函数的泛型,交给了泛型委托的T,T类型的参数也传进来
T res = func(x, y);
Console.WriteLine(res);
}
}
}
3、LINQ串讲:
十三、类
1、什么是“类”
class-declaration:
attributesopt class-modifiersopt partialopt class identifier type-parameter-listopt
class-baseopt type-parameter-constraints-clausesopt class-body ;opt
【1】是一种数据结构(data structure);
类是一种数据结构,它可以包含数据成员(常量和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数)以及嵌套类型。类类型支持继承,继承是一种机制,它使派生类可以对基类进行扩展和专业化。 ——《C#语言规范》
【2】是一种数据类型;
类是一种引用类型,具体到每一个类上,每一个类都是一个自定义的引用类型。类可以声明变量,可以声明实例。
/用new创建实例/
namespace HelloClass {
class Program {
static void Main(string[] args) {
Student stu = new Student(1,“LGQ”);
stu.Report();
}
}
class Student {
public Student(int id, string name) { //实例构造器
this.ID = id;
this.Name = name;
}
public int ID { get; set; }
public string Name { get; set; }
public void Report() {
Console.WriteLine("I'm "+this.ID+" student, my name is "+this.Name+".");
}
}
}
/反射/
namespace HelloClass {
class Program {
static void Main(string[] args) {
/类是一种数据类型/
Type t = typeof(Student); //Type类型类,t是类名。一个typeof(Student)类型的t类
object o = Activator.CreateInstance(t, 1, “LGQ”); //CreateInstance()创建实例的话,都是object类型的实例
Console.WriteLine(o.GetType().Name);
} //当调用结束后,对象就没人引用了,然后析构器就会被调用
}
class Student {
public Student(int id, string name) { //实例构造器
this.ID = id;
this.Name = name;
}
~Student() { //析构器
Console.WriteLine("Bye bye! Release the system resources...");
}
public int ID { get; set; }
public string Name { get; set; }
public void Report() {
Console.WriteLine("I'm "+this.ID+" student, my name is "+this.Name+".");
}
}
}
/Dynamic编程/
namespace HelloClass {
class Program {
static void Main(string[] args) {
Type t = typeof(Student);
dynamic stu = Activator.CreateInstance(t, 1, “LGQ”);
Console.WriteLine(stu.Name);
} //当调用结束后,对象就没人引用了,然后析构器就会被调用
}
class Student {
public Student(int id, string name) { //实例构造器
this.ID = id;
this.Name = name;
}
~Student() { //析构器
Console.WriteLine("Bye bye! Release the system resources...");
}
public int ID { get; set; }
public string Name { get; set; }
public void Report() {
Console.WriteLine("I'm "+this.ID+" student, my name is "+this.Name+".");
}
}
}
/类中类/
namespace HelloClass {
class Program {
static void Main(string[] args) {
/类是一种数据类型/
Type t = typeof(Student); //Type类型类,t是类名。一个typeof(Student)类型的t类
object o = Activator.CreateInstance(t, 1, “LGQ”); //CreateInstance()创建实例的话,都是object类型的实例
Student stu = o as Student; //将类型Stu从类型转变回类
Console.WriteLine(stu.Name); //这样就可以输出类stu的Name属性
} //当调用结束后,对象就没人引用了,然后析构器就会被调用
}
class Student {
public Student(int id, string name) { //实例构造器
this.ID = id;
this.Name = name;
}
//~Student() { //析构器
// Console.WriteLine("Bye bye! Release the system resources...");
//}
public int ID { get; set; }
public string Name { get; set; }
public void Report() {
Console.WriteLine("I'm "+this.ID+" student, my name is "+this.Name+".");
}
}
}
【3】代表现实世界中的“种类”;
2、构造器与析构器
析构器:释放内存中的某些资源。
【1】实例;
/析构器:/
namespace HelloClass {
class Program {
static void Main(string[] args) {
Student stu = new Student(1,“LGQ”);
stu.Report();
} //当调用结束后,对象就没人引用了,然后析构器就会被调用
}
class Student {
public Student(int id, string name) { //实例构造器
this.ID = id;
this.Name = name;
}
~Student() { //析构器
Console.WriteLine("Bye bye! Release the system resources...");
}
public int ID { get; set; }
public string Name { get; set; }
public void Report() {
Console.WriteLine("I'm "+this.ID+" student, my name is "+this.Name+".");
}
}
}
【2】静态;
十四、类的声明,继承和访问控制:
1、类声明的全貌:
【1】C#类声明的位置
【2】声明既定义(C#与Java)
2、最简单的类声明:
【1】类的访问控制
3、类的继承:
【1】类在功能上的扩展(extend)
namespace HelloOOP
{
class Program
{
static void Main(string[] args)
{
Type t = typeof(Car);
Type tb = t.BaseType;
Type tTop = tb.BaseType;
Console.WriteLine(tTop.FullName);
}
}
class Vehicle
{
}
class Car : Vehicle
{
}
}
namespace HelloOOP
{
class Program
{
static void Main(string[] args)
{
Car car = new Car();
Console.WriteLine(car is Vehicle);
}
}
class Vehicle
{
}
class Car : Vehicle
{
}
}
【2】只能继承一个基类,但可以实现多个与其接口(规范用语)
【3】类访问级别对继承的影响
【4】sealed类不能被继承
【5】特点:
[*一个子类的实例也是父类的实例。父类的实例不一定是子类的实例]
[*所有类最底层的基类就是Object]
[*1、子类对父类成员的全盘继承,并依次传递下去,类成员只能增加不能移除的特点]
[*2、派生类的访问级别不能高于基类,只能小于等于]
[*3、继承就是子类在完整接收父类成员的前提下对父类进行的横向和纵向的扩展]
[*横向扩展:是对类成员个数的扩充。纵向扩展:是对类成员版本的更新(即重写)。]
4、成员的继承与访问:
【1】派生类对基类的成员获得与访问。
namespace HelloOOP
{
class Program
{
static void Main(string[] args)
{
Car car = new Car();
Console.WriteLine(car.Owner); //先从基类的构造器开始执行,构造基类对象,然后再一级一级向下进行构造,最终构造出来这个子类对象
}
}
class Vehicle //创建基类,这几个类中的顶端
{
public Vehicle()
{
this.Owner = "N/A";
}
public string Owner { get; set; }
}
class Car : Vehicle //派生类
{
public Car() //构造函数
{
this.Owner = "Car Owner";
}
public void ShowOwner() //打印出Owner的名字
{
Console.WriteLine(base.Owner); //base:只能访问上一级基类的对象。因为子类把基类的所有成员继承下来了,因此this和base指向的是同一个地址
}
}
}
【2】在派生类中访问基类的成员。
【3】构造器的不可继承。
namespace HelloOOP
{
class Program
{
static void Main(string[] args)
{
Car car = new Car("Liang");
Console.WriteLine(car.Owner); //先从基类的构造器开始执行,构造基类对象,然后再一级一级向下进行构造,最终构造出来这个子类对象
}
}
class Vehicle //创建基类,这几个类中的顶端
{
public Vehicle(string owner) //带参数的构造器,系统不会再提供默认构造器
{
this.Owner = owner;
}
public string Owner { get; set; }
}
class Car : Vehicle //派生类
{
//public Car():base("N/A") //构造函数
//{
// this.Owner = "Car Owner";
//}
public Car(string owner):base(owner)
{
}
public void ShowOwner() //打印出Owner的名字
{
Console.WriteLine(base.Owner); //base:只能访问上一级基类的对象。因为子类把基类的所有成员继承下来了,因此this和base指向的是同一个地址
}
}
}
[*类成员的访问级别不能高于类的]
5、面向对象的实现风格:
【1】Class-based
【2】Prototype-based