C# 学习参考文档和开发工具
微软c#官方文档:https://learn.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/
开发工具:Visual Studio(VS)
C# 概述
C# 概述
- C#(发音为 "C sharp")是一种新式编程语言,不仅面向对象,还类型安全。
- 它是由 微软 (Microsoft)开发的。
- C# 编程是基于 C 和 C++ 编程语言的, 源于 C 语言系列 。
- 虽然 C# 的构想十分接近于传统高级语言 C 和 C++,是一门面向对象的编程语言, 但是它与 Java 非常相似 。
- C# 程序在 .NET 上运行 。
- C# 文件的后缀为 .cs。
C# 特性
简单:虽然 C# 的构想十分接近于传统高级语言 C 和 C++,是一门面向对象的编程语言, 但是它与 Java 非常相似 。所以它容易上手
类型安全:C# 允许动态分配轻型结构的对象和内嵌存储。 C# 支持泛型方法和类型,因此增强了类型安全性和性能。
兼容: C# 有统一类型系统。 所有 C# 类型(包括
int
和double
等基元类型)均继承自一个根object
类型。 所有类型共用一组通用运算。 任何类型的值都可以一致地进行存储、传输和处理。 此外,C# 还支持用户定义的引用类型和值类型。版本控制:C# 强调版本控制,以确保程序和库以兼容方式随时间推移而变化。 C# 设计中受版本控制加强直接影响的方面包括:单独的
virtual
和override
修饰符,关于方法重载决策的规则,以及对显式接口成员声明的支持。
C# 流行原因:
- 现代的、通用的编程语言。
- 面向对象。
- 面向组件。
- 容易学习。
- 结构化语言。
- 它产生高效率的程序。
- 它可以在多种计算机平台上编译。
- .Net 框架的一部分。
C# 一些重要的功能:
- 布尔条件(Boolean Conditions)
- 自动垃圾回收(Automatic Garbage Collection)
- 标准库(Standard Library)
- 组件版本(Assembly Versioning)
- 属性(Properties)和事件(Events)
- 委托(Delegates)和事件管理(Events Management)
- 易于使用的泛型(Generics)
- 索引器(Indexers)
- 条件编译(Conditional Compilation)
- 简单的多线程(Multithreading)
- LINQ 和 Lambda 表达式
- 集成 Windows
c# 快速入门~C# 和 java 的不同
1、入门demo的hello world
using System;
namespace HelloWorldApplication
{
class HelloWorld
{
static void Main(string[] args)
{
/* 我的第一个 C# 程序*/
Console.WriteLine("Hello World");
Console.ReadKey();
}
}
}
using 关键字用于在程序中包含 System 命名空间。 一个程序一般有多个 using 语句。
命名空间声明(Namespace declaration)
最后一行 Console.ReadKey(); 是针对 VS.NET 用户的。这使得程序会等待一个按键的动作,防止程序从 Visual Studio .NET 启动时屏幕会快速运行并关闭
☺ 如果是通过 Visual Studio 写的c# 项目:
目录结构:
解决方案:相当于项目的工程空间,工程空间是可以有多个项目的
.vs文件: 是和项目配置相关的
后缀 .sln 文件:是解决方案文件,通过该文件可以打开解决方案
项目中后缀是 .cs 文件:就是咱用 c# 写的代码
☺ 对于hello world 程序
(1) using
- 在任何 C# 程序中的第一条语句都是:using System;
(2) namespace
2、注释
- 和java 是一样的~
- 补充:关键词 #region 和 #endregion 用来管理注释
3、变量命名规范
- 除了数字、字母,还可以是@
4、C# 的变量类型:还包含了指针类型
在 C# 中,变量分为以下几种类型:
- 值类型(Value types)
- 引用类型(Reference types)
- 指针类型(Pointer types)
引用类型:
内置的引用类型有:object、dynamic 和 string。
■ 动态(Dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。
声明动态类型的语法:dynamic <variable_name> = value;
- 举例子:dynamic d = 20;
☺ 字符串(String)类型:还有@ 修饰,@有其相对的意义
- 字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。
- 举例子1:使用引号的
String str = "runoob.com";
举例子2:使用@,意义:
(1)取消 \ 在字符串中的转义作用,使其单纯的表示为一个‘\’。
(2)将字符串按照编辑的原格式输出。
string str = @"C:\Windows"; //相当于string str = "C:\\Windows";
//作用2:使其按照编辑的原格式输出
Console.WriteLine(@"你好啊!
我的朋友!");
Console.ReadKey();
//输出结果为:
你好啊!
我的朋友!
■ 指针类型(Pointer types)
C# 中的指针与 C 或 C++ 中的指针有相同的功能。语法:type* identifier;
举例子:
char* cptr;
int* iptr;
■ 用户自定义引用类型有:class、interface 或 delegate。
5、定义常量,使用关键词 const
6、占位符的
Console.WriteLine的后半部的参数变量的顺序就是对应 {0}、{1}、{2}、{3}...
举例子:
int n1 = 10;
int n2 = 20;
int n3 = 30;
Console.WriteLine("参数结果是:{0},{1},{2}", n1, n2, n3);
Console.ReadKey();
//输出结果为:
参数结果是:10,20,30
7、C# 的运算符,和java 不太一样的是它还有其他运算符
对于算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符 和java是一样的
其他运算符
:
8、C# 的访问权限-private、protected、internal、protected internal、public
和java有点点不一样的是,default 权限在C# 被叫为internal,并且c# 多了一个访问权限为protected internal
其他的都是差不多一样的: private、protected、internal、protected internal、public
☺ 9、C# 方法的参数传递,ref关键词的使用,实现参数作为引用类型,out关键字的使用,实现参数作为输出类型
- c# 方法的定义,调用和java 是一模一样的
- 参数的传递和 java 也是一模一样的,有三种情况:值参数、引用参数、输出参数
☺ 输出参数的作用:方法没有返回值时,而需要从该方法中返回结果的时候,需要使用输出参数
■ ref 类型的使用,实现参数作为引用类型
:
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(ref a, ref b);//-------这里使用了关键词ref,实现了参数作用引用类型--------
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
- 结果:
在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:100
在交换之后,b 的值:200
■ out关键字的使用,实现参数作为输出类型
:
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValue(out int x )
{
int temp = 5;
x = temp;
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
Console.WriteLine("在方法调用之前,a 的值: {0}", a);
/* 调用函数来获取值 */
n.getValue(out a);//-------这里使用了关键词out,实现了实现参数作为输出类型--------
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.ReadLine();
}
}
}
- 结果:
在方法调用之前,a 的值: 100
在方法调用之后,a 的值: 5
10、不定长参数-关键词 params+数组
public 返回类型 方法名称( params 类型名称[] 数组名称 )
- 举例子:
using System;
namespace ArrayApplication
{
class ParamArray
{
public int AddElements(params int[] arr) //-----------不定长参数----------
{
int sum = 0;
foreach (int i in arr)
{
sum += i;
}
return sum;
}
}
class TestClass
{
static void Main(string[] args)
{
ParamArray app = new ParamArray();
int sum = app.AddElements(512, 720, 250, 567, 889);
Console.WriteLine("总和是: {0}", sum);
Console.ReadKey();
}
}
}
11、可空类型--单问号 ? 与 双问号 ??
■ 可空类型(Nullable Type)表示在值类型的正常取值范围内再加上一个null值.
- 举例子:
int i; //默认值0
int? ii; //默认值null,可空的int
//-----------------------
int? i = 3; // 相当于Nullable<int> i = new Nullable<int>(3);
■ 一个?和两个?的区别:
- 一个问号:用于定义值变量是可空的值变量。
- 两个问号:用于判断一个可空的变量当前值是否为null,若是null,则返回指定的值。
a = b ?? c //b是一个可空的值变量,如果 b 为 null,则 a = c,如果 b 不为 null,则 a = b
double? num1 = null;
double num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34
12、二维数组的写法有点不同
static void Main(string[] args)
{
/* 一个带有 5 行 2 列的数组 */
int[,] a = new int[5, 2] {{0,0}, {1,2}, {2,4}, {3,6}, {4,8} };
int i, j;
/* 输出数组中每个元素的值 */
for (i = 0; i < 5; i++)
{
for (j = 0; j < 2; j++)
{
Console.WriteLine("a[{0},{1}] = {2}", i, j, a[i,j]);
}
}
Console.ReadKey();
}
■ Array 类的常用方法:
Array 类的方法
- Array 类的常用属性:Length 或 LongLength
13、c# 中还有结构体,在 C# 中的结构与传统的 C 或 C++ 中的结构不同。
■ 在 C# 中的结构与传统的 C 或 C++ 中的结构不同:
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构可实现一个或多个接口。
- 结构成员不能指定为 abstract、virtual 或 protected。
- 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
- 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
■ 类 vs 结构:
类是引用类型,结构是值类型。
结构不支持继承。
结构不能声明默认的构造函数。
■ 举例子:
//声明 Book 结构
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
//使用结构体
public class testStructure
{
public static void Main(string[] args)
{
Books Book1; /* 声明 Book1,类型为 Books */
/* book 1 详述 */
Book1.title = "C Programming";
Book1.author = "Nuha Ali";
Book1.subject = "C Programming Tutorial";
Book1.book_id = 6495407;
/* 打印 Book1 信息 */
Console.WriteLine( "Book 1 title : {0}", Book1.title);
Console.WriteLine("Book 1 author : {0}", Book1.author);
Console.WriteLine("Book 1 subject : {0}", Book1.subject);
Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);
}
}
■ 类 vs 结构的使用场景:
▷ 结构和类的适用场合分析:
- 1、当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
- 2、对于点、矩形和颜色这样的
轻量对象
,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低; - 3、在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
- 4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。
14、c# 的类和java是一模一样的,就是多了个析构函数
c#中的类,类的定义-成员变量、成员方法,类的构造函数,类的实例化、调用类的成员变量、方法,都是和java一模一样的!
析构函数:
- 析构函数的名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。
- 析构函数用于在结束程序(比如关闭文件、释放内存等)之前
释放资源
。析构函数不能继承或重载。
举例子:
using System;
namespace LineApplication
{
class Line
{
private double length; // 线条的长度
public Line() // 构造函数
{
Console.WriteLine("对象已创建");
}
~Line() //析构函数
{
Console.WriteLine("对象已删除");
}
public void setLength( double len )
{
length = len;
}
public double getLength()
{
return length;
}
static void Main(string[] args)
{
Line line = new Line();
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
}
}
}
- 结果:
对象已创建
线条的长度: 6
对象已删除
15、C# 的继承、实现,还有抽象类,重写抽象类的方法的写法以及 abstract 和 virtual 的区别(最后总结一下,在C#中子类和父类的同名方法的关系)
■ 继承语法:
<访问修饰符> class <基类>
{
...
}
class <派生类> : <基类>
{
...
}
- 子类继承父类,语法:
class 子类: 父类
- 子类继承父类的构造方法,语法:
public 子类(参数列表): base(参数列表){}
- 子类调用父类中的普通的方法(非构造方法),语法:
子类声明的方法{
base.父类声明的方法(参数列表);
}
■ 举例子:
using System;
namespace InheritanceApplication
{
class Shape
{
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
protected int width;
protected int height;
}
// 派生类
class Rectangle: Shape
{
public int getArea()
{
return (width * height);
}
}
}
■ 接口interface实现的语法:
//声明为接口,使用的关键词还是 interface
// 接口 PaintCost
public interface PaintCost
{
int getCost(int area);//接口中的方法,是没有任何修饰符
}
//子类实现接口,是使用冒号,代替java的implements
// 派生类
class Rectangle : PaintCost
{
public int getCost(int area)
{
return area * 70;
}
}
■ 子类继承抽象类,重写抽象类的方法的写法:重写abstract
修饰的方法,要加override
abstract class Shape
{
abstract public int area();
}
class Rectangle: Shape, PaintCost
{
public override int area ()
{
Console.WriteLine("Rectangle 类的面积:");
return 100;
}
}
■ abstract 和 virtual 的区别:
▷ 区别点:
abstract 声明的方法-抽象方法必须存在于抽象类中,抽象类中的抽象方法在声明的时候是不可以有实现体的。对于子类继承了抽象类,就必须重写人家所有的抽象方法!
而 virtual 声明的方法,没有要求一定需要存在什么类中,可以存在抽象父类、普通父类中,并且要求声明为 virtual 方法的同时,需要有实现体!对于子类继承了父类,可以重写或者不重写 父类 virtual 声明的方法。
virtual和abstract 它们有一个共同点:如果用来修饰方法,前面必须添加public,要不然就会出现编译错误:虚拟方法或抽象方法是不能私有的。
▷ 举例子:
//abstract
abstract class BaseTest1
{
public abstract void fun();
}
class DeriveTest1: BaseTest2
{
public override void fun() { }//对于子类继承了抽象类,就必须重写人家所有的抽象方法!
}
//virtual
class BaseTest2
{
public virtual void fun() { }//必须有实现
}
class DeriveTest2: BaseTest1
{
//public override void fun() { } //对于子类继承了父类,可以重写或者不重写 父类 virtual 声明的方法。
}
☺ 最后总结,在C#中子类和父类的同名方法的关系,C# 细分了,重写和覆盖是不同的,这里和java 不一样,在java中重写就是覆盖,覆盖就是重写!
☺ (1) 重写(关键词是override)
- 重写的意义:实现多态性。
多态的意思是,父类引用指向子类实例的时候。当子类重写了父类的这个方法。那么父类的引用调用是子类的方法。
//代码中的写法,多态的写法:
父亲 p = new 子类();
//接着调用同名方法
p.sameFunction();
//这时候,如果子类中和父类的同名方法,没有使用关键词override 进行修饰,那么实际上调用的是父类的方法
-------------------------------------------------- 举例子-----------------------------------------------
using System;
namespace TestApplication
{
public class Shape
{
public void ordinaryFunction()
{
Console.WriteLine(" Shape中的ordinaryFunction");
}
}
public class Rectangle: Shape
{
public new void ordinaryFunction()//子类中和父类的同名方法,没有使用关键词override 进行修饰,即没有重写的作用
{
Console.WriteLine(" Rectangle中的ordinaryFunction");
}
}
class RectangleTester
{
static void Main(string[] args)
{
Shape s = new Rectangle();//Rectangle 没有重写的情况下,调用的是父类的方法
s.ordinaryFunction();
Console.ReadKey();
}
}
}
(2) 覆盖(关键词是new)
- 覆盖的意义:其实就隐藏,因为子类中的方法【返回值、参数列表情况都和父类一模一样,这就不是重构了,本质上,子类和父类的这个同名方法是两个不同的方法】,覆盖的作用就是直接隐藏掉父类的方法,直接调用子类方法。
public class Shape
{
public void ordinaryFunction()
{
Console.WriteLine(" Shape中的ordinaryFunction");
}
}
public class Rectangle : Shape
{
public new void ordinaryFunction() //重写基类的同名方法要加上new,否则会有警告
{
Console.WriteLine(" Rectangle中的ordinaryFunction");
}
}
☺ 16、总结 c#的重要的关键词 new、virtual、override、abstract、interface
■ new关键字,目的是为了隐藏父类同名同参数的方法。不写也可以,但会有警告,建议写一下。
- 因为默认就会覆盖子类的方法。覆盖的意思是子类可以调用父类的公开和受保护的方法的。但是万一子类有一个同名同参数的方法,这个时候就不再调用父类继承过来的方法,而是调用自己的方法。
■ virtual方法和普通方法一样,但是加上 virtual 后就允许子类重写 override。
■ override目的就是为了多态。
- 多态是父类引用指向子类对象,调用的是子类的方法体。如果没有 override 那么还是得用父类的方法。
- 多态的实现可以使用 virtual+override 或者 abstract+override 或者 接口+实现(没有 override) 或者 override+override
- override 的源头方法 只有两种 virtual和 abstract
■ abstract方法是抽象方法,没有方法体,抽象方法必须存在抽象类里。
- 抽象类可以有非抽象方法 和属性 字段等。抽象类是不能 new 的。它的抽象方法没有方法体的。
■ interface接口可以有属性。但是不能有字段。方法也是没有修饰符的。没有方法体。
- 实现了接口的类的方法不需要用 override 关键字的。
17、C# 命名空间 namespace
- 和java 的包名作用都是一样了,都是为了实现代码的复用。
- 我们知道,重用性(reusebility)是软件工程中一个非常重要的目标。重用,不仅仅指自己所写的软件(代码、组件等等)可以被重复利用;更广义的重用是指不同的人,不同的团队,不同的公司之间可以互相利用别人的成果。另外,对于大型软件,往往是由多个团队共同开发的,这些团队有可能分布于不同的城市、地区、甚至国家。由于这些原因,名字管理成为一个非常重要的因素。
18、预处理
C# 预处理器指令列表
19、C# 正则表达式
20、异常
- 异常处理的关键词和java是一样的,都是那几个关键词:try、catch、finally 和 throw。
- 不过,异常的根类和继承关系有点不同,自定义异常的时候,要注意一下是应用异常,还是系统异常,然后再继承该异常类:
21、反射结合了特性一起使用
(1) 优缺点:
优点:
- 1、反射提高了程序的灵活性和扩展性。
- 2、降低耦合性,提高自适应能力。
- 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
- 1、性能问题:使用反射基本上是一种解释操作,
用于字段和方法接入时要远慢于直接代码
。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。 - 2、使用反射会模糊程序内部逻辑;
程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术
,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
(2) 特性
预定义特性(Attribute)
Net 框架提供了三种预定义特性:
- AttributeUsage
- Conditional
- Obsolete
AttributeUsage
▪ 预定义特性 AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。
▪ 规定该特性的语法如下:
[AttributeUsage(
validon,
AllowMultiple=allowmultiple,
Inherited=inherited
)]
- 参数 validon 规定特性可被放置的语言元素。它是枚举器
AttributeTargets
的值的组合。默认值是 AttributeTargets.All。 - 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
- 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
22、访问器
get 访问器、set 访问器
// 声明类型为 string 的 Name 属性
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 简写为:
public string Name {get; set;}
- 举例子:
using System;
namespace runoob
{
public abstract class Person
{
public abstract string Name{get; set;}
public abstract int Age{get; set;}
}
class Student : Person
{
private string name = "N.A";
private int age = 0;
// 声明类型为 string 的 Name 属性
public override string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 声明类型为 int 的 Age 属性
public override int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return Name = " + Name + ", Age = " + Age;
}
}
}
23、委托,关键词 delegate
☺ 其实底层就是使用的是反射!
■ 委托:类似于 C 或 C++ 中函数的指针
!即用变量调方法
。好处:使方法的使用变得更加灵活。
- 某个方法自己不调用,而将方法自己委托给另一个变量,由这个变量执行这个方法,执行结果和这个方法自己执行是一样的
■ 委托在实际使用中的例子:
- 自定义排序
- 窗体传值
- 多线程操作
■ 举例子:
- 声明为委托类型,语法:
public|internal delegate 返回值 委托名(参数列表);
- 对于声明为委托类型的方法,是需要使用new 关键词进行实例化创建的,然后使用变量进行引用,最后通过该变量执行
//声明为委托类型
delegate int NumberChanger(int n);
--------------------------------------------------------------------
public static int AddNum(int p)
{
num += p;
return num;
}
--------------------------------------------------------------------
// 使用new 创建委托实例 或者直接使用变量指向方法
NumberChanger nc1 = new NumberChanger(AddNum);//相当于 NumberChanger nc1 = AddNum;
// 使用委托对象调用方法,执行AddNum 方法
nc1(25);
■ 委托的多播,简单理解,理解成java中的链式调用即可
- 举例子:
using System;
delegate int NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
// 创建委托实例
NumberChanger nc;
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
nc = nc1;
nc += nc2;
// 调用多播
nc(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
- 结果:
Value of Num: 75
24、事件【设计模式-发布订阅模式】
(1) 为什么需要事件?
- 原因1:
看看前面的委托的声明的语法,声明为委托类型的时候,修饰符是 public|internal
而有时候,程序为了安全考虑,需要私有化,即封装起来,不给外界随意调用,随意修改!
- 原因2:
委托链可以累加方法,+= 误用了 = 那么只会执行最后一个=的方法,不安全,有被覆盖的隐患
☺ 官网对事件的概述:
事件是一种特殊的多播委托,仅可以从声明事件的类(或派生类)或结构( 发布服务器类
)中对其进行调用。
如果其他类或结构订阅该事件,则在发布服务器类引发该事件时,将调用其事件处理程序方法。
- 例子1:发布服务器类
//关于如何声明和引发使用 [EventHandler] 作为基础委托类型的事件
public class SampleEventArgs
{
public SampleEventArgs(string text) { Text = text; }
public string Text { get; } // readonly
}
public class Publisher
{
// Declare the delegate (if using non-generic pattern).
public delegate void SampleEventHandler(object sender, SampleEventArgs e);
// Declare the event.
public event SampleEventHandler SampleEvent;
// Wrap the event in a protected virtual method
// to enable derived classes to raise the event.
protected virtual void RaiseSampleEvent()
{
// Raise the event in a thread-safe manner using the ?. operator.
SampleEvent?.Invoke(this, new SampleEventArgs("Hello"));
}
}
- 例子2:就是在例子1的基础上,在发布服务器类中,添加了监听方法 onXX事件
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;//真实的事件处理方法
for(int i = 0; i < 10; i++)
{
Console.WriteLine("adding one");
c.Add(1);
}
}
/**
* 真实的事件处理方法
*/
static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)
{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
Environment.Exit(0);
}
}
/**
* 事件参数类
* EventArgs: 是C# 内置的类,内部有一个委托属性 public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);
*/
public class ThresholdReachedEventArgs : EventArgs
{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}
class Counter
{
//声明事件
public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
private int threshold;
private int total;
public Counter(int passedThreshold)
{
threshold = passedThreshold;
}
public void Add(int x)
{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);//监听事件
}
}
/**
*监听事件
*/
protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)
{
EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
if (handler != null)
{
handler(this, e);//事件处理程序
}
}
}
}
(2) 事件的本质
private delegate EventHandler<LogHandlerArgs> LogHandler;
public event EventHandler<LogHandlerArgs> LogHandler
{
add
{
LogHandler += value;//添加到事件的方法队列中
}
remove
{
LogHandler -= value;
}
}
(3) 事件具有以下属性:
事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项。
发行者确定何时引发事件;订户确定对事件作出何种响应。
一个事件可以有多个订户。 订户可以处理来自多个发行者的多个事件。
没有订户的事件永远也不会引发。
当事件具有多个订户时,引发该事件时会同步调用事件处理程序。
在 .NET 类库中,事件基于 EventHandler 委托和 EventArgs 基类。
25、C# 不安全代码
当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。不安全代码或非托管代码是指使用了指针变量的代码块。
如果本文对你有帮助的话记得给一乐点个赞哦,感谢!