c# 委托 事件 lambda表达式

委托

C/C++中的函数指针实例:

typedef int (*Calc)(int a, int b); //这里必须加括号
int Add(int a, int b)
{
    return a + b;
}
int Sub(int a, int b)
{
    return a - b;
}
int main()
{
    int x = 100;
    int y = 200;
    int z = 0;
    Calc funcPoint1 = &Add;
    Calc funcPoint2 = ⋐
    z = funcPoint1(x, y);
    cout << z << endl;
    z = funcPoint2(x, y);
    cout << z << endl;
}

委托实例

class Program
    {
        
        static void Main(string[] args)
        {
            Calculator c = new Calculator();
            //Action委托:没有返回参数
            Action action = new Action(c.PrintInfo);
            c.PrintInfo();
            action.Invoke();
            action();
            //Func委托:有返回参数
            Func<int, int, int> f = new Func<int, int, int>(c.Add);
            Func<int, int, int> f2 = new Func<int, int, int>(c.Sub);
            
            Console.WriteLine(f(1, 2));
            Console.WriteLine(f.Invoke(1, 2));
            Console.WriteLine(f2(1, 2));
            Console.WriteLine(f2.Invoke(1, 2));
        }
        

    }
    class Calculator
    {
        public void PrintInfo()
        {
            Console.WriteLine("this class has 3 methods");
        }
        public int Add(int a, int b)
        {
            return a + b;
        }
        public int Sub(int a, int b)
        {
            return a - b;
        }
    }
class Program
{
    static void Main(string[] args)
    {
        Action<string> action = new Action<string>(SayHello);
        action("Tim");
    }
    static void SayHello(string name)
    {
        Console.WriteLine($"Hello, {name1}!");
	
    }
}
//可以用var关键字来缩短代码:
class Program
{
    static void Main(string[] args)
    {
        var action = new Action<string, int>(SayHello);
        action("Tim");
    }
    static void SayHello(string name, int rount)
    {
      	for(int i=0; i<round; ++i)
      	{
        	Console.WriteLine($"Hello, {name1}!");
        }

    }
}

Console.WriteLine后面 “$”的作用

//复杂麻烦的写法
        string str1 = "my name is " + name + ",my age is " + age + ".";
        //使用Format的写法
        string str2 = string.Format("my name is {0},my age is {1}.", name, age);
        //使用$语法糖的写法
        string str3 = $"my name is {name},my age is {age}.";

public delegate double Calc(double x, double y);
    class Program
    {
        
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            double x = 200;
            double y = 100;
            double z;
            Calc calc1 = new Calc(calculator.Add);
            Calc calc2 = new Calc(calculator.Sub);
            Calc calc3 = new Calc(calculator.Mul);
            Calc calc4 = new Calc(calculator.Div);
            z = calc1(x, y);
            Console.WriteLine(z);
            z = calc2.Invoke(x, y);
            Console.WriteLine(z);
            z = calc3.Invoke(x, y);
            Console.WriteLine(z); 
            z = calc4.Invoke(x, y);
            Console.WriteLine(z);
        }
        

    }
    class Calculator
    {
        
        public double Add(double a, double b)
        {
            return a + b;
        }
        public double Sub(double a, double b)
        {
            return a - b;
        }
        public double Mul(double a, double b)
        {
            return a * b;
        }
        public double Div(double a, double b)
        {
            return a / b;
        }
    }

模板方法案例

class Program
{
    
    static void Main(string[] args)
    {
        ProductFactory pf = new ProductFactory();
        WrapFactory wf = new WrapFactory();
        Func<Product> func1 = new Func<Product>(pf.MakePizza);
        Func<Product> func2 = new Func<Product>(pf.MakeToyCar);

        Box box1 = wf.WrapProduct(func1);
        Box box2 = wf.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; }
}
class WrapFactory
{
    public Box WrapProduct(Func<Product> getProduct)
    {
        Box box = new Box();
        box.Product = getProduct.Invoke();
        return box;
    }
}
class ProductFactory
{
    public Product MakePizza()
    {
        Product product = new Product();
        product.Name = "Pizza";
        return product;
    }
    public Product MakeToyCar()
    {
        Product product = new Product();
        product.Name = "Toy Car";
        return product;
    }
}

回调方法案例

class Program
{
    
    static void Main(string[] args)
    {
        ProductFactory pf = new ProductFactory();
        WrapFactory wf = new WrapFactory();
        Func<Product> func1 = new Func<Product>(pf.MakePizza);
        Func<Product> func2 = new Func<Product>(pf.MakeToyCar);
        Logger logger = new Logger();
        Action<Product> action = new Action<Product>(logger.log);
        Box box1 = wf.WrapProduct(func1, action);
        Box box2 = wf.WrapProduct(func2, action);
        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);

    }
}
class Logger
{
    public void log(Product product)
    {
        Console.WriteLine("Product {0} created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price); 
    }
}
class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}
class Box
{
    public Product Product { get; set; }
}
class WrapFactory
{
    public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback)
    {
        Box box = new Box();
        Product product = getProduct.Invoke();
        if(product.Price>=50)
        {
            logCallback.Invoke(product);
        }
        box.Product = product;
        return box;
    }
}
class ProductFactory
{
    public Product MakePizza()
    {
        Product product = new Product();
        product.Name = "Pizza";
        product.Price = 12;
        return product;
    }
    public Product MakeToyCar()
    {
        Product product = new Product();
        product.Name = "Toy Car";
        product.Price = 100;
        return product;
    }
}

委托的高级使用

多播委托案例

class Program
{

    static void Main(string[] args)
    {
        Student stu1 = new Student() { ID=1, PenColor = ConsoleColor.Yellow};
        Student stu2 = new Student() { ID=1, PenColor = ConsoleColor.Green};
        Student stu3 = new Student() { ID=1, 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 += action2;
        action1 += action3;
        action1.Invoke();
    }
}
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);
        }
    }
}

隐式异步调用 -- BeginInvoke

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.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);
        }
    }
}

显式异步调用

注:ctrl+. 给没有定义命名空间的类添加命名空间

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 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显式异步调用
						Action action1 = new Action(stu1.DoHomeWork);
            Action action2 = new Action(stu2.DoHomeWork);
            Action action3 = new Action(stu3.DoHomeWork);
          	Task task1 = new Task(action1);
            Task task2 = new Task(action2);
            Task task3 = new Task(action3);
            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);
            }
        }
    }

可以使用接口来取代委托

以上面的回调方法中的例子作为改进:

class Program
{

    static void Main(string[] args)
    {
        IProductFactory pizzaFactory = new PizzaFactory();
        IProductFactory toycarFactory = new ToycarFactory();
        WrapFactory wf = new WrapFactory();
        
        Box box1 = wf.WrapProduct(pizzaFactory);
        Box box2 = wf.WrapProduct(toycarFactory);
        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);

    }
}
interface IProductFactory
{
    Product Make();
}
class PizzaFactory : IProductFactory
{
    public Product Make()
    {
        Product product = new Product();
        product.Name = "Pizza";
        product.Price = 12;
        return product;
    }
}
class ToycarFactory : IProductFactory
{
    public Product Make()
    {
        Product product = new Product();
        product.Name = "Toy Car";
        product.Price = 100;
        return product;
    }
}
class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}
class Box
{
    public Product Product { get; set; }
}
class WrapFactory
{
    public Box WrapProduct(IProductFactory productFactory)
    {
        Box box = new Box();
        Product product = productFactory.Make();
        box.Product = product;
        return box;
    }
}

自己定义委托类型

class Program
{

    static void Main(string[] args)
    {
        MyDele dele1 = new MyDele(M1);
      	Student stu = new Student();
      	//dele1 += stu.SayHello;
      	//同上
				dele1 += (new Studnet()).SayHello;
      	//dele1.Invoke();
      	//同上,与上面相比下面直接调用方法更常用。
      	dele1();
    }
  	static void M1()
  	{
    	Console.Write("M1 is called");
    }
  	class Student{
    	public void SayHello(){
      	Concole.WriteLine("Hello, I'm a student!");
    	}
  	}
  	delegate void MyDele();
}

自定义泛型委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            MyDele<int> myDele = new MyDele<int>(Add);
            int addRed = myDele(2, 4);
            Console.WriteLine(addRed);
            MyDele<double> myDele2 = new MyDele<double>(Mul);
            double mulRed = myDele2(2.2, 2.2);
            Console.WriteLine(mulRed);
            //查看委托是否是类
            Console.WriteLine(myDele.GetType().IsClass);
        }
        static int Add(int x, int y)
        {
            return x + y;
        }
        static double Mul(double x, double y)
        {
            return x * y;
        }
    }
    delegate T MyDele<T>(T a, T b);
}

事件

事件的概念

微软把这种经由事件发送出来的与事件本身相关的数据称为:事件参数

根据通知和事件参数来采取行动的这种行为称为响应事件或处理事件。处理事件时具体所做的事情就叫做事件处理器

事件的应用

案例1.
class Program
    {
        static void Main(string[] args)
        {
            //timer: 事件的拥有者
            Timer timer = new Timer();
            timer.Interval = 1000;
            // boy和girl: 事件的响应者
            Boy boy = new Boy();
            //timer.Elapsed: 事件成员; boy.Action: 事件处理器;+=: 事件订阅
            timer.Elapsed += boy.Action;
            Girl girl = new Girl();
            timer.Elapsed += girl.Action;
            //timer.Enabled = true;
            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:图一颗星方式
class Program
    {
        static void Main(string[] args)
        {
            //form: 事件拥有者
            Form form = new Form();
            //controller: 事件响应者
            Controller controller = new Controller(form);
            form.ShowDialog();
        }
    }
    class Controller
    {
        private Form form;
        public Controller(Form form)
        {
            this.form = form;
            //this.form.Click: 事件; +=: 事件订阅
            this.form.Click += this.FormClicked;
        }
        //FormClicked:事件处理器
        private void FormClicked(object sender, EventArgs e)
        {
            this.form.Text = DateTime.Now.ToString();
        }
    }
案例3:图两颗星方式
class Program
    {
        static void Main(string[] args)
        {
            //form: 事件拥有者和响应者
            MyForm form = new MyForm();
            form.Click += form.FormClicked;
            form.ShowDialog();
        }
    }
    class MyForm : Form
    {
        internal void FormClicked(object sender, EventArgs e)
        {
            this.Text = DateTime.Now.ToString();
        }
    }
案例4:图三颗星方式
class Program
    {
        static void Main(string[] args)
        {
            //form: 事件拥有者和响应者
            MyForm form = new MyForm();        
            form.ShowDialog();
        }
    }
    this Form: 响应者
    class MyForm : Form
    {
        private TextBox textBox;
        private Button button;
        public MyForm()
        {
            this.textBox = new TextBox();
            //this.button: 拥有者
            this.button = new Button();
            this.Controls.Add(this.textBox);
            this.Controls.Add(this.button);
            this.button.Click += this.ButtonClicked;
            this.button.Text = "say hello";
            this.button.Top = 50;
        }

        public void ButtonClicked(object sender, EventArgs e)
        {
            this.textBox.Text = "Hello World!!!!!!!!!!!!!!!!!!";
        }
    }
在窗体应用程序中,使用事件的五种方法
namespace EventWindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            //button2. 方法2. 手动订阅
            this.button2.Click += buttonClick;
            //button3. 方法3.委托
            this.button3.Click += new EventHandler(buttonClick);
            //button4. 方法4. 旧式委托
            this.button4.Click += delegate (object sender, EventArgs e)
              {
                  this.myTextBox.Text = "button4Clicked!";
              };
            //button5. 方法5. lambda表达式
            this.button5.Click += (sender, e) =>
              {
                  this.myTextBox.Text = "button5Clicked!";
              };
        }

        //button1. 方法1.直接在设计中双击click自动生成订阅
        private void buttonClick(object sender, EventArgs e)
        {
            if(sender==button1)
            {
                this.myTextBox.Text = "hello!";
            }
            if(sender==button2)
            {
                this.myTextBox.Text = "world!";
            }
            if(sender==button3)
            {
                this.myTextBox.Text = "My.Okey!";
            }
        }
    }
}

事件的定义

事件声明的完整格式

namespace FullFormOfEvent
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;
            //一定是事件拥有者的内部逻辑触发了该事件
            customer.Action();
            customer.PayThisBill();
        }

        
    }

    public class OrderEventArgs:EventArgs
    {
        public string DishName { get; set; }
        public string Size { get; set; }
    }

    //声明委托类型 为声明的order事件用的
    //当一个委托专门用来声明事件时,名称一般为事件名+EventHandler
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

    public class Customer
    {
        //声明一个委托类型的字段 引用事件处理器的
        private OrderEventHandler orderEventHandler;

        public event OrderEventHandler Order
        {
            add
            {
                this.orderEventHandler += value;
            }
            remove
            {
                this.orderEventHandler -= value;
            }
        }

        public double Bill { get; set; }
        public void PayThisBill()
        {
            Console.WriteLine("I will pay ${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++)
            {
                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.SitDown();
            this.Think();

        }


    }
    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 = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

事件声明的简略格式

只修改了两个部分

namespace FullFormOfEvent
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;
            //一定是事件拥有者的内部逻辑触发了该事件
            customer.Action();
            customer.PayThisBill();
        }
    }

    public class OrderEventArgs:EventArgs
    {
        public string DishName { get; set; }
        public string Size { get; set; }
    }

    //声明委托类型 为声明的order事件用的
    //当一个委托专门用来声明事件时,名称一般为事件名+EventHandler
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
    public class Customer
    {
// 修改1
        public event OrderEventHandler Order;

        public double Bill { get; set; }
        public void PayThisBill()
        {
            Console.WriteLine("I will pay ${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++)
            {
                Console.WriteLine("Let me think。。。");
                Thread.Sleep(1000);
            }
//修改2
            //和详细声明相比,现在用了一个事件的名字取代了过去这个字段上的名字
            //之前的委托类型字段还在,只不过没有出现在代码中
            if(this.Order!=null)
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                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 you the dish - {0}.", e.DishName);
            double price = 10;
            switch (e.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

省去了委托类型的声明,而是使用了通用的EventHandler

namespace FullFormOfEvent
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;
            //一定是事件拥有者的内部逻辑触发了该事件
            customer.Action();
            customer.PayThisBill();
        }

        
    }
    public class OrderEventArgs:EventArgs
    {
        public string DishName { get; set; }
        public string Size { get; set; }
    }
    public class Customer
    {
        //省去了委托类型的声明,而是使用了通用的EventHandler
        public event EventHandler Order;

        public double Bill { get; set; }
        public void PayThisBill()
        {
            Console.WriteLine("I will pay ${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++)
            {
                Console.WriteLine("Let me think。。。");
                Thread.Sleep(1000);

            }
            //和详细声明相比,现在用了一个事件的名字取代了过去这个字段上的名字
            //之前的委托类型字段还在,只不过没有出现在代码中
            if(this.Order!=null)
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                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(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;
            switch (orderInfo.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

事件与委托的关系

lambda表达式

是匿名和inline方法

//lambda表达式:方法1
Func<int, int, int> func = new Func<int, int, int>((int a, int b)=> { return a + b; });
int res = func(3, 5);
Console.WriteLine($"a+b={res}");
//方法2
func = new Func<int, int, int>((x, y) => { return x * y; });
res = func(3, 5);
Console.WriteLine($"a*b={res}");
//方法3 最常用
func = (x, y) => { return x - y; };
res = func(3, 5);
Console.WriteLine($"a-b={res}");

使用函数来调用lambda表达式(泛型委托的类型参数推断)

DoSomeCalc((a, b)=> { return a + b; }, 100, 200);
static void DoSomeCalc<T>(Func<T, T, T>func, T x, T y)
{
    T res = func(x, y);
    Console.WriteLine(res);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值