[C.C++] C#事件详解

694 0
Honkers 2026-2-14 04:16:32 来自手机 | 显示全部楼层 |阅读模式

一、※初步了解事件

1.事件的定义:

事件英文解释为:a thing that happens,especially something important.

中文通顺的理解为:能够发生的什么事情。

例:手机可以发生响铃这一事情,响铃就是一个事件。

2.事件是一种什么角色:

事件使对象或类具备通知能力的成员。

以手机(对象)举例:手机可以响铃,响铃(事件)使手机具备了通知能力。

3.事件使用(事件是干嘛的):

用于对象或类间的动作协调与信息传递。

4.原理:

事件模型中的两个5

1)5个部分

“发生→响应”中的五个部分:闹钟响铃了小明起床。

其中4个部分很容易看出:闹钟,响铃,小明,起床

其中还有一个隐含的部分为:订阅     

也就是小明订阅了他手机的铃声,手机响铃时,小明才会发生反应。如果是别人的手机,小明没有订阅,就算响铃,小明也不会有反应。

2)5个动作

“发生→响应”中的五个动作:①我有一个事件→②一个人或一群人关心我这个事件→③我的这个事件发生了→④关心这个事件的人会被依次通知到→⑤被通知到的人根据拿到的事件信息(事件参数)对事件进行响应(处理事件)。

5.※※提示:

事件多用于桌面、手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的。

各种编程语言对这个机制的实现方法不尽相同。

Java语言里没有事件这种成员,也没有委托这种数据类型。Java的事件是用接口来实现的。

MVC、MVP、MVVM等模式,是事件模式更高级、更有效的“玩法”。

日常开发的时候,使用已有事件的机会比较多,自己声明的机会比较少,所以先学怎么使用。

二、事件的应用

1.实例的一个简单演示

+= 就是订阅事件的操作符,用法看下列代码

  1. ussng System;
  2. using System.Timers;//需要引入命名空间
  3. namespace 事件_简单实例
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. Timer timer = new Timer();
  10. timer.Interval = 1000;//设置时间间隔
  11. Boy boy = new Boy();
  12. //Elapsed 就是每间隔Interval时间,就触发一次事件
  13. timer.Elapsed += boy.Action;//用boy订阅timer的Elapsed事件,处理事件为Action方法
  14. timer.Start();
  15. Console.ReadLine();
  16. }
  17. }
  18. class Boy
  19. {
  20. internal void Action(object sender, ElapsedEventArgs e)
  21. {
  22. Console.WriteLine("Jump");
  23. }
  24. }
  25. }
复制代码

也可以多个对象订阅同一个事件,对上面代码稍加改动,如下:

  1. using System;
  2. using System.Timers;
  3. namespace 事件_简单实例
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. Timer timer = new Timer();
  10. timer.Interval = 1000;//设置时间间隔
  11. Boy boy = new Boy();
  12. timer.Elapsed += boy.Action;
  13. Girl girl = new Girl();
  14. timer.Elapsed += girl.GirlAction;
  15. timer.Start();
  16. Console.ReadLine();
  17. }
  18. }
  19. class Boy
  20. {
  21. internal void Action(object sender, ElapsedEventArgs e)
  22. {
  23. Console.WriteLine("Jump");
  24. }
  25. }
  26. class Girl
  27. {
  28. internal void GirlAction(object sender, ElapsedEventArgs e)
  29. {
  30. Console.WriteLine("Sing!");
  31. }
  32. }
  33. }
复制代码

2.事件模型的五个组成部分

1)五个组成部分

①事件的拥有者(event source,对象)

②事件成员(event,成员)

③事件的响应者(event subscriber,对象)

④事件处理器(event handler,成员)

⑤事件订阅----把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”

2)四种关联模式

这五个部分又存在4种关联模式:

第一种:

本图的意思是:事件的拥有者和事件的响应者是两个完全不同的对象。代码示例如下:

代码目的:当点击窗体时,Text会显示出当前的时间。

  1. using System;
  2. using System.Windows.Forms;
  3. namespace 事件_1
  4. {
  5. static class Program
  6. {
  7. static void Main()
  8. {
  9. Form form = new Form();//事件的拥有者
  10. Controller controller = new Controller(form);//事件的响应者
  11. form.ShowDialog();//为了让窗口停下
  12. }
  13. }
  14. class Controller
  15. {
  16. private Form form;
  17. public Controller(Form form)
  18. {
  19. if(form != null)
  20. {
  21. this.form = form;
  22. this.form.Click += this.FromClicked;//订阅
  23. }
  24. }
  25. //事件处理器
  26. private void FromClicked(object sender, EventArgs e)
  27. {
  28. this.form.Text = DateTime.Now.ToString();
  29. }
  30. }
  31. }
复制代码

第二种:

图解:事件的拥有者和事件的响应者是同一个对象。

代码目的和第一种一样。但是由于这个对象需要有事件处理器,而Form这个类是微软给出的,无法对其修改,所以这里可以采用继承。

继承:可以简单理解为MyForm里拥有Form里的所有代码,只不过没有展现出来,然后又在这基础上再写代码。 (继承还有其它东西,可以转移阵地学习)

  1. using System;
  2. using System.Windows.Forms;
  3. namespace 事件_2
  4. {
  5. static class Program
  6. {
  7. static void Main()
  8. {
  9. MyForm form = new MyForm();
  10. form.Click += form.FormClicked;
  11. form.ShowDialog();
  12. }
  13. }
  14. class MyForm : Form //继承
  15. {
  16. internal void FormClicked(object sender, EventArgs e)
  17. {
  18. this.Text = DateTime.Now.ToString();
  19. }
  20. }
  21. }
复制代码

第三种:

 图解:事件的响应者是一个对象,事件的拥有者是事件响应者这个对象里的一个字段成员。

代码目的:当点击按钮时,textbox里会显示一串字符。

  1. using System;
  2. using System.Windows.Forms;
  3. namespace 事件_3
  4. {
  5. static class Program
  6. {
  7. static void Main()
  8. {
  9. MyForm myForm = new MyForm();
  10. myForm.ShowDialog();
  11. }
  12. }
  13. class MyForm:Form
  14. {
  15. private TextBox textBox;
  16. private Button button;
  17. public MyForm()
  18. {
  19. this.textBox = new TextBox();
  20. this.button = new Button();
  21. this.Controls.Add(this.button);//把button添加到窗口上
  22. this.Controls.Add(this.textBox);
  23. this.button.Click += this.ButtonClicked;
  24. this.button.Text = "Sey Hellow";
  25. this.button.Top = 100;//button与窗口最上边的距离
  26. }
  27. private void ButtonClicked(object sender, EventArgs e)
  28. {
  29. this.textBox.Text = "Hellow,World!";
  30. }
  31. }
  32. }
复制代码

三、事件的声明

1.事件的声明

1)完整声明

用代码来模拟顾客点菜。顾客点菜,服务员处理点菜这个事件。

  1. using System;
  2. // 引入线程命名空间,用于调用Thread.Sleep()实现程序延迟
  3. using System.Threading;
  4. // 命名空间:组织代码,避免类名冲突,本示例用于封装事件相关代码
  5. namespace 事件_完整声明
  6. {
  7. // 程序入口类:控制台程序的核心入口,存放Main方法
  8. class Program
  9. {
  10. // 程序唯一入口方法:程序运行时自动优先执行该方法
  11. static void Main(string[] args)
  12. {
  13. // 1. 实例化事件发送者:创建顾客对象(发起点餐事件的主体)
  14. Customer customer = new Customer();
  15. // 2. 实例化事件处理者:创建服务员对象(响应点餐事件的主体)
  16. Waiter waiter = new Waiter();
  17. // 3. 事件绑定:将服务员的Action方法(事件处理程序)绑定到顾客的Order事件上
  18. // 语法:事件对象 += 事件处理程序(方法签名必须与委托一致)
  19. customer.Order += waiter.Action;
  20. // 4. 调用顾客行为方法,间接触发Order事件
  21. customer.Action();//触发这个事件
  22. // 5. 调用支付账单方法,展示事件处理后的结果(账单金额)
  23. customer.PayTheBill();
  24. }
  25. }
  26. // 事件参数类:用于传递事件触发时的附加数据(点餐信息)
  27. // 命名规范:事件名+EventArgs,必须继承自System.EventArgs(C#事件参数约定)
  28. public class OrderEventArgs : EventArgs
  29. {
  30. // 自动属性:存储菜品名称
  31. public string DishName { get; set; }
  32. // 自动属性:存储菜品份量
  33. public string Size { get; set; }
  34. }
  35. // 事件委托:约定事件处理程序的方法格式(模板/契约)
  36. // 命名规范:事件名+EventHandler,标准签名:(事件发送者, 事件参数),无返回值
  37. public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
  38. // 顾客类:事件发送者,负责声明和触发Order事件,封装顾客的属性和行为
  39. public class Customer
  40. {
  41. // 委托字段:用于存储绑定到Order事件上的所有事件处理程序引用(多播委托)
  42. // 【注意问题1】:public修饰违背封装原则,外部可直接修改,建议改为private
  43. public OrderEventHandler orderEventHandler;
  44. // 声明事件:使用event关键字,基于OrderEventHandler委托类型
  45. // 事件是委托的封装,外部仅能通过+=/-=绑定/移除处理程序,无法直接调用或赋值
  46. public event OrderEventHandler Order
  47. {
  48. // 添加访问器:外部使用+=绑定事件处理程序时,执行此逻辑
  49. add
  50. {
  51. // 【注意问题2】:直接用=赋值违背委托多播特性,会覆盖之前绑定的处理程序
  52. // 正确写法:this.orderEventHandler += value;
  53. this.orderEventHandler = value;
  54. }
  55. // 移除访问器:外部使用-=移除事件处理程序时,执行此逻辑
  56. remove
  57. {
  58. // 【注意问题2】:直接用=赋值无法正确移除指定处理程序,会覆盖委托引用
  59. // 正确写法:this.orderEventHandler -= value;
  60. this.orderEventHandler = value;
  61. }
  62. }
  63. // 自动属性:存储顾客的账单金额
  64. public double Bill { get; set; }
  65. // 支付账单方法:输出顾客需要支付的金额
  66. public void PayTheBill()
  67. {
  68. // 格式化输出账单金额,this.Bill表示当前顾客实例的账单属性
  69. Console.WriteLine("I will pay ${0}.", this.Bill);
  70. }
  71. // 顾客行为:进店
  72. public void Walkln()//进店
  73. {
  74. Console.WriteLine("Walk into the restaurant.");
  75. }
  76. // 顾客行为:坐下
  77. public void SitDown()//坐下
  78. {
  79. Console.WriteLine("Sit down");
  80. }
  81. // 顾客行为:思考点餐(核心:触发Order事件的逻辑)
  82. public void Think()//思考吃什么
  83. {
  84. // 循环5次,模拟顾客思考的延迟过程
  85. for (int i = 0; i < 5; i++)
  86. {
  87. Console.WriteLine("Let me think...");
  88. // 暂停程序1000毫秒(1秒),模拟思考耗时
  89. Thread.Sleep(1000);
  90. }
  91. // 判断委托字段是否为null(是否已绑定事件处理程序)
  92. // 【注意问题3】:多线程场景下存在线程安全问题,建议先复制到局部变量再判断
  93. if (orderEventHandler != null)
  94. {
  95. // 1. 创建事件参数对象,封装点餐的附加数据
  96. OrderEventArgs e = new OrderEventArgs();
  97. // 2. 给事件参数赋值(菜品名称:宫保鸡丁)
  98. e.DishName = "Kongpao Chicken";
  99. // 3. 给事件参数赋值(菜品份量:大份)
  100. e.Size = "large";
  101. // 4. 调用委托的Invoke方法,触发事件,执行绑定的所有事件处理程序
  102. // 参数1:this(当前Customer实例,事件发送者)
  103. // 参数2:e(事件参数,点餐数据)
  104. this.orderEventHandler.Invoke(this, e);
  105. }
  106. }
  107. // 顾客完整行为流程:整合进店、坐下、思考点餐
  108. public void Action()
  109. {
  110. // 【注意问题4】:无实际业务意义,暂停程序等待用户输入,建议移除
  111. Console.ReadLine();
  112. // 调用进店方法
  113. this.Walkln();
  114. // 调用坐下方法
  115. this.SitDown();
  116. // 调用思考点餐方法(间接触发Order事件)
  117. this.Think();
  118. }
  119. }
  120. // 服务员类:事件处理者,负责响应Order事件,处理账单计算逻辑
  121. public class Waiter
  122. {
  123. // 事件处理程序:响应Customer的Order事件
  124. // 【关键】:方法签名必须与OrderEventHandler委托完全一致(参数、返回值)
  125. internal void Action(Customer customer, OrderEventArgs e)
  126. {
  127. // 输出响应信息,告知用户已接收到点餐请求
  128. Console.WriteLine("I will serve you the dish {0}", e.DishName);
  129. // 定义菜品基础价格
  130. double price = 10;
  131. // 根据菜品份量调整最终价格
  132. switch (e.Size)
  133. {
  134. // 小份:基础价格*0.5
  135. case "small":
  136. price *= 0.5;
  137. break;
  138. // 大份:基础价格*1.5
  139. case "large":
  140. price *= 1.5;
  141. break;
  142. // 默认分支:价格不变,保证程序健壮性
  143. default:
  144. break;
  145. }
  146. // 将计算后的菜品价格累加到顾客的账单中
  147. customer.Bill += price;
  148. }
  149. }
  150. }
复制代码

注意:和委托有关的类,比如事件拥有者,响应者,事件信息,都应该和委托的访问权限相同,也就是public。

这个例子中,就可以看出来事件是基于委托的。

事件是基于委托的:1.事件需要用委托类型来做一个约束,这个约束既规定了事件能发送什么消息给响应者,也规定了响应者能收到什么样的事件消息,这就决定了事件响应者的事件处理器必须能够给这个约束匹配上,才能够订阅这个事件。
2.当事件的响应者给事件的拥有者提供了能够匹配这个事件的事件处理器之后,得找个地方把事件处理器保存或者记录下来,能够记录或者引用方法的任务也只有委托类型的实例能够做到。
 

2)简略声明

像字段一样的声明格式,不是字段

学习简略声明之前,先看一个东西(用的完整声明的代码):

 事件触发这段代码中,把orderEventHandler(委托)换成Order(事件)后,会报这样一个错误:

这说明,事件的右边只能是+=或者-=。

下面开始写简略声明的代码(在完整声明的基础上修改): 

只需要修改两处即可:

修改为 

  1. public event OrderEventHandler Order;//简略声明
复制代码

 这里的orderEventHandler修改为Order。

看到这你就会发现,事件后面不是只能加+=或者-=?为啥这里就能直接改,这就是微软设计的这个语法糖带来的影响。注意一下就行。

语法糖:简单点来说就是微软经过一系列操作,让语法写起来更简单。

2.思考:为什么需要事件

思考:有了委托字段,为什么还需要事件?

答:为了程序的逻辑更加“有道理”,更加安全,谨防“借刀杀人”。

怎么理解呢,下面来解释一下:

  1. public event OrderEventHandler Order;//简略声明
复制代码

当你把这行代码中的event删掉后,你会发现,程序还是能运行,而且运行结果一模一样。这时候,这行代码就是一个委托字段,那么为什么还需要事件。

下面我把主方法修改一下,写出下列代码:

  1. using System;
  2. using System.Threading;
  3. namespace 事件_简略声明
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. Customer customer = new Customer();
  10. Waiter waiter = new Waiter();
  11. customer.Order += waiter.Action;
  12. OrderEventArgs e1 = new OrderEventArgs();
  13. e1.DishName = "Manhanquanxi";
  14. e1.Size = "large";
  15. OrderEventArgs e2 = new OrderEventArgs();
  16. e2.DishName = "Beer";
  17. e2.Size = "large";
  18. Customer badGuy = new Customer();
  19. badGuy.Order += waiter.Action;
  20. badGuy.Order.Invoke(customer, e1);
  21. badGuy.Order.Invoke(customer, e2);
  22. customer.PayTheBill();
  23. }
  24. }
  25. public class OrderEventArgs : EventArgs
  26. {
  27. public string DishName { get; set; }
  28. public string Size { get; set; }
  29. }
  30. public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
  31. public class Customer
  32. {
  33. public OrderEventHandler Order;//简略声明
  34. public double Bill { get; set; }
  35. public void PayTheBill()
  36. {
  37. Console.WriteLine("I will pay ${0}.", this.Bill);
  38. }
  39. //下面几个方法用来触发事件
  40. public void Walkln()
  41. {
  42. Console.WriteLine("Walk into the restaurant.");
  43. }
  44. public void SitDown()
  45. {
  46. Console.WriteLine("Sit down");
  47. }
  48. public void Think()
  49. {
  50. for (int i = 0; i < 5; i++)
  51. {
  52. Console.WriteLine("Let me think...");
  53. Thread.Sleep(1000);
  54. }
  55. if (Order != null)
  56. {
  57. OrderEventArgs e = new OrderEventArgs();
  58. e.DishName = "Kongpao Chicken";
  59. e.Size = "large";
  60. //触发事件处理器
  61. this.Order.Invoke(this, e);
  62. }
  63. }
  64. public void Action()
  65. {
  66. Console.ReadLine();
  67. this.Walkln();
  68. this.SitDown();
  69. this.Think();
  70. }
  71. }
  72. public class Waiter
  73. {
  74. internal void Action(Customer customer, OrderEventArgs e)
  75. {
  76. Console.WriteLine("I will serve you the dish {0}", e.DishName);
  77. double price = 10;
  78. switch (e.Size)
  79. {
  80. case "small":
  81. price *= 0.5;
  82. break;
  83. case "large":
  84. price *= 1.5;
  85. break;
  86. default:
  87. break;
  88. }
  89. customer.Bill += price;
  90. }
  91. }
  92. }
复制代码

你会发现,badGuy点的菜算在了customer的头上,这就是“借刀杀人”。有了事件之后,把某个对象的某个事件和某个对象的某个事件处理器捆绑起来,只有你这个点菜事件触发时,相对应事件处理器才会触发,价钱才会算在自己头上。

3.事件的本质

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

这个包装器对委托字段的访问起限制作用。

封装的一个重要功能就是隐藏。

事件对外隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能。(就是只能+=,-=)

添加/移除事件处理器的时候可以直接使用方法名,这是委托实例不具备的功能。

4.命名规范

1)事件委托的命名

 除非是一个非常通用的事件约束,例如:微软帮我们声明了一个委托EventHandler.

其实,上面这一段委托没必要声明,因为微软给了一个EventHandler。

先看一下EventHandler的定义:

object是所有类的基类,所以第一个参数可以接收任何类型的参数。(不懂的先学一下继承)

而我们声明的事件信息那个类,派生于EventArges,所以声明的事件信息那个类型可以传递进来。

下面开始写代码:

  1. using System;
  2. using System.Threading;
  3. namespace 事件_简略声明
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. Customer customer = new Customer();
  10. Waiter waiter = new Waiter();
  11. customer.Order += waiter.Action;
  12. customer.Action();
  13. customer.PayTheBill();
  14. }
  15. }
  16. public class OrderEventArgs : EventArgs
  17. {
  18. public string DishName { get; set; }
  19. public string Size { get; set; }
  20. }
  21. public class Customer
  22. {
  23. public event EventHandler Order;
  24. public double Bill { get; set; }
  25. public void PayTheBill()
  26. {
  27. Console.WriteLine("I will pay ${0}.", this.Bill);
  28. }
  29. public void Walkln()
  30. {
  31. Console.WriteLine("Walk into the restaurant.");
  32. }
  33. public void SitDown()
  34. {
  35. Console.WriteLine("Sit down");
  36. }
  37. public void Think()
  38. {
  39. for (int i = 0; i < 5; i++)
  40. {
  41. Console.WriteLine("Let me think...");
  42. Thread.Sleep(1000);
  43. }
  44. if (Order != null)
  45. {
  46. OrderEventArgs e = new OrderEventArgs();
  47. e.DishName = "Kongpao Chicken";
  48. e.Size = "large";
  49. this.Order.Invoke(this, e);
  50. }
  51. }
  52. public void Action()
  53. {
  54. Console.ReadLine();
  55. this.Walkln();
  56. this.SitDown();
  57. this.Think();
  58. }
  59. }
  60. public class Waiter
  61. {
  62. internal void Action(object sender, EventArgs e)
  63. {
  64. Customer customer = sender as Customer;
  65. OrderEventArgs orderInfo = e as OrderEventArgs;
  66. Console.WriteLine("I will serve you the dish {0}", orderInfo.DishName);
  67. double price = 10;
  68. switch (orderInfo.Size)
  69. {
  70. case "small":
  71. price *= 0.5;
  72. break;
  73. case "large":
  74. price *= 1.5;
  75. break;
  76. default:
  77. break;
  78. }
  79. customer.Bill += price;
  80. }
  81. }
  82. }
复制代码

这样,我们就省去了自己来声明委托类型,直接用厂商给出的就行,所以大家还需要对EventHandler进行进一步学习。当然直接声明委托也没毛病

2)委托参数的命名

委托的参数一般有两个:第一个是object类型,名字为sender(上面那个例子就用到了),实际上就是事件的拥有者。第二个是EventArgs的派生类,参数名一般都为e,也就是最前面说的事件参数。

3)触发事件的方法的命名:

触发事件的方法的命名一般命名为On+事件名。

这个方法的访问级别为protected,不能为public,所以要对以前的代码进行修改,而且还要遵循一个方法干一件事儿的编程思想!

对下面这段代码修改:

  1. public void Think()//思考吃什么
  2. {
  3. for (int i = 0; i < 5; i++)
  4. {
  5. Console.WriteLine("Let me think...");
  6. Thread.Sleep(1000);
  7. }
  8. //if (Order != null)
  9. //{
  10. // OrderEventArgs e = new OrderEventArgs();
  11. // e.DishName = "Kongpao Chicken";
  12. // e.Size = "large";
  13. // //触发事件处理器
  14. // this.Order.Invoke(this, e);
  15. //}
  16. OnOrder("Kongpao chicken", "large");
  17. }
  18. protected void OnOrder(string dishName,string size)
  19. {
  20. if (Order != null)
  21. {
  22. OrderEventArgs e = new OrderEventArgs();
  23. e.DishName = dishName;
  24. e.Size = size;
  25. //触发事件处理器
  26. this.Order.Invoke(this, e);
  27. }
  28. }
复制代码
4)事件的命名约定

带有时态的动词或者动词短语。

事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用过去时。

※例:closing和closed,一个是关闭中,一个是关闭后,关闭中这个事件发生时,事件处理器可能时提示用户是否要保存文件,关闭后这个事件发生时,事件处理器可能要记录一下用户用这个软件用了多长时间等等。

四、事件与委托的关系

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

答案是否定的,只是声明的时候看起来像,事件只是简化声明了而已。

为什么会造成这一错觉,是因为1.事件声明的时候用了委托类型,简化声明时与委托字段很相似,很多人就把event看做成了一个像public一样修饰符,其实event就是一个声明事件的关键字。

2.订阅事件的时候+=操作符后面可以是一个委托委托实例(技术比较老),这与委托实例的赋值的方法相同,所以也容易误解。

用一段代码解释一下第二点:

事件的订阅在很久以前用的是下面这种方法,是不是和委托实例赋值的方法相同。
 

  1. customer.Order += new EventHandler(waiter.Action);
复制代码

2.为什么要用委托类型来声明事件

站在source的角度来看,是为了表明source能对外传递哪些消息。

站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样的签名方法来处理(响应)事件。

委托类型的实例将用于储存(引用)事件处理器。

3.对比事件与属性

属性不是字段,很多时候属性是字段的包装器,这个包装器来保护字段不被滥用。

事件不是委托字段,事件是委托字段的包装器,这个包装器用来保护委托字段不被滥用。

包装器永远都不可能是被包装的东西。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行