【C#】跟prism相关其它知识点

1.interface 接口

类可以通过 : 符号来实现一个或多个接口。类必须实现接口中定义的所有成员。
一个类可以实现多个接口。)

在这里插入代码片

2.IEventAggregator事件

IEventAggregator是一种用于实现事件聚合的设计模式。
在 Prism 框架 中,IEventAggregator 是一个核心组件,用于实现发布/订阅模式,帮助开发者实现松耦合的组件通信。
它允许组件通过发布和订阅事件进行通信,而不需要直接引用彼此。少组件之间的耦合,使代码更易于维护和扩展
常用于流程与界面之间进行通信【事件发布与处理】

需要注意:需要项目中先加入prism框架。

  • 导入库文件,可以参考 (【如何新建一个应用模块】)

  • 定义事件
    事件是一个继承自 PubSubEvent 的类,其中 T 是事件参数的类型。

//测试发布订阅流程:第一步:定义一个自定义事件类
public class MyEventClass : PubSubEvent<string>
{
	// 可以留空,除非需要特殊实现
}
  • 发布事件
//测试发布订阅流程:第二步:在需要触发事件的类中发布事件
public class TestServiceClass
{
    private readonly IEventAggregator _eventAggregator;


    public TestServiceClass(IEventAggregator eventAggregator) {
        _eventAggregator = eventAggregator;
    }

    public void SendMessage(string message)
    {
        // 获取事件并发布**********************************************************
        _eventAggregator.GetEvent<MyEventClass>().Publish(message);
    }
}
  • 订阅事件
//测试发布订阅流程:第三步:在需要接收事件的类中订阅事件
public  class TestViewModel :  BindableBase					//这里需要改一下
{
    public string ReceivedMessage { get; private set; }

    private IContainerExtension _container;
    private IEventAggregator _eventAggregator;
    public TestViewModel(IContainerProvider containerProvider,IEventAggregator eventAggregator)
    {
        //_container = containerProvider;
        _eventAggregator = eventAggregator;
        // 订阅事件******************************************************************
        _eventAggregator.GetEvent<MyEventClass>().Subscribe(OnMessageReceived);

    }

    private void OnMessageReceived(string message)
    {
        ReceivedMessage = message;
        // 处理接收到的消息...
    }
}
  • 使用
	TestServiceClass testServiceClass = new(_eventAggregator);
    testServiceClass.SendMessage("testServiceClass.SendMessage");


2.1高级用法:

//带条件的订阅
// 1.只有当消息以"Important:"开头时才处理
eventAggregator.GetEvent<MessageSentEvent>().Subscribe(OnImportantMessageReceived, 
              ThreadOption.PublisherThread, 
              false, 
              message => message.StartsWith("Important:"));
              
//2. 在UI线程上处理事件(适用于需要更新UI的情况)
eventAggregator.GetEvent<MessageSentEvent>()
    .Subscribe(OnMessageReceived, ThreadOption.UIThread);

//3. 使用强引用(默认是弱引用,设置为true表示强引用)
eventAggregator.GetEvent<MessageSentEvent>()
    .Subscribe(OnMessageReceived, ThreadOption.PublisherThread, true);

//4.取消引用
private SubscriptionToken _subscriptionToken;

public SubscriberViewModel(IEventAggregator eventAggregator)
{
    _subscriptionToken = eventAggregator.GetEvent<MessageSentEvent>()
        .Subscribe(OnMessageReceived);
}

public void Unsubscribe()
{
    eventAggregator.GetEvent<MessageSentEvent>()
        .Unsubscribe(_subscriptionToken);			//*************************************************
}

3.IContainerExtension[views,viewModule,service]

IContainerExtension 是 Prism 框架中用于抽象依赖注入(DI)容器的接口,它提供了一种统一的方式来注册和解析依赖项。【主要跟界面有关】
用法:

  • 注册依赖:将接口与实现类注册到容器中。
  • 解析依赖:从容器中获取已注册的实例。
  • 管理生命周期:支持单例(Singleton)、瞬态(Transient)等生命周期模式。

a.定义接口和实现类的定义.
b.APP中注册依赖《复制即可》
c.在试图模型中(viewModel)解析依赖
d.视图xaml绑定viewModel《复制即可》

可以在App中注册,将功能类service,UI显示view.xaml,UI接口viewModel.cs,形成模块。

4.BindableBase-prism

属于MVVM的知识点,为 ViewModel 提供:1.自动实现 INotifyPropertyChanged 接口。2.无需手动编写属性变更通知逻辑。3.使用 SetProperty 方法轻松管理属性变更。4.

使用:1.让你的 ViewModel 继承自 BindableBase。2.对于需要通知 UI 的属性,使用 SetProperty 方法。3.可以可选地实现属性变更的回调方法。

using Prism.Mvvm;

public class UserViewModel : BindableBase
{
    private string _name;
    private int _age;
    
    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
    
    public int Age
    {
        get => _age;
        set => SetProperty(ref _age, value);
    }
}

一些相关的高级用法:

5.DelegateCommand-prism

DelegateCommand在xaml与viewModel之间进行通信,可将方法直接作为命令的执行逻辑,不需要创建单独的类【用于xaml和viewModel之间】

private DelegateCommand _clickCommand;
public DelegateCommand ClickCommand =>
    _clickCommand ?? (_clickCommand = new DelegateCommand(ExecuteClick));

private void ExecuteClick()
{
    // 执行点击操作
    System.Diagnostics.Debug.WriteLine("按钮被点击了!");
}
<Button
    Grid.Row="2"
    Grid.Column="0"
    Command="{Binding ClickCommand}"
    Content="按钮点击" />

另外一种简化版本方法:

public DelegateCommand<string> ClickCommand_2 { get; private set; }
private void ExecuteClick_2(string obj)
{
    Console.WriteLine("按钮2按下!");
}

//功能按钮【在构造函数中加入】
ClickCommand_2 = new DelegateCommand<string>(ExecuteClick_2);
<Button
    Grid.Row="3"
    Grid.Column="0"
    Command="{Binding ClickCommand_2}"
    Content="按钮点击2" />

6.Lambda表达式与简化写法

Lambda表达式:

// 传统委托写法
Func<int, int> square1 = delegate (int x) { return x * x; };

// Lambda表达式写法
Func<int, int> square2 = x => x * x;
Console.WriteLine(square2(5)); // 输出25

//多参数的Lambda表达式的用法
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // 输出7

//语句Lambda
Action<string> greet = name =>
{
    //使用语句块
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};

greet("world"); //输出

//其他简化写法1.方法组转换
//传统写法
List<string> names = new List<string> { "Alice","Bob","Charlie"};
names.ForEach(delegate(string name) {Console.WriteLine(name);});
//方法组转换简化
names.ForEach(Console.WriteLine);

//其他简化写法2.对象初始化简化
//传统写法
Person p1 = new Person();
p1.Name = "Alice";
p1.Age = 25;
//简化写法
Person p2 = new Person { Name = "Alice",Age = 25};


//其他简化写法3.集合初始化简化
//传统写法
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
//简化写法
List<int> list = new List<int> { 1, 2, 3 };

//其他简化写法4.空条件运算符(?.)
Person person= new Person { Name = "Alice", Age = 25 }; ;
//传统写法
if((person != null) &&  (person.Name!=null))
{
    Console.WriteLine(person.Name);
}
//简化写法
Console.WriteLine(person?.Name);

//其他简化写法5.空合并运算符(??)
//传统写法
string name = (person != null) ? person.Name : "Unkown";
//简化写法
string name1 = person?.Name ?? "Unknown";

//LINQ中的Lambda表达式
List<int> numberslist = new List<int> { 1, 2, 3 };
//筛选偶数
var events = numberslist.Where(x => x % 2 == 0);
//计算平方
var squares = numberslist.Select(x => x * x);
//排序
var ordered = numbers.OrderByDescending(x => x);

//局部函数与Lambda结合使用
bool isvalid(int x) => x > 0 && x < 100;
List<int> data = new List<int> { 0, 12 };
var validData = data.Where(x => isvalid(x));

//表达式体成员
//传统属性
public string _name;
public string Name
{
    get { return _name; }
}

//表达式体属性
public string _name_2;
public string Name_2 => _name_2;

//表达式体方法
public int Square(int x) => x * x;

7.Action关键字 <为内置的委托类型>,Func

Action 是 C# 中的一个内置委托类型,用于表示没有返回值的方法。它可以用于封装方法并作为参数传递,是实现回调、事件处理和异步编程的常用工具。
无参数Action:

//无参Action的用法: 

// 第一步:定义一个无参数的方法
void Greet()
{
    Console.WriteLine("Hello, World!");
}

public void Test()
{
    // 创建 Action 实例
    Action greetAction = Greet;

    // 调用 Action
    greetAction();  // 输出: Hello, World!
}

带参数Action:

// 有参Action的用法: 第一步: 定义一个带参数的方法
void PrintSum(int a, int b)
{
    Console.WriteLine($"Sum: {a + b}");
}

public void Test_int()
{
    // 创建带参数的 Action
    Action<int, int> sumAction = PrintSum;

    // 调用 Action
    sumAction(5, 3);  // 输出: Sum: 8
}

使用场景:

public void TestAction_2_1()
{
    //1.作为方法参数
    void ProcessNumbers(int x, int y, Action<int, int> operation)
    {
        Console.WriteLine("Processing numbers...");
        operation(x, y);
    }
    void Add(int a, int b) => Console.WriteLine($"Addition: {a + b}");
    void Multiply(int a, int b) => Console.WriteLine($"Multiplication: {a * b}");
    // 使用
    ProcessNumbers(4, 5, Add);        // 输出: Processing numbers... Addition: 9
    ProcessNumbers(4, 5, Multiply);   // 输出: Processing numbers... Multiplication: 20
}
public void TestAction_2_2()
{
    //2.使用匿名方法
    Action<string> printAction = delegate (string message)
    {
        Console.WriteLine(message.ToUpper());
    };

    //使用
    printAction("hello");   //输出:HELLO
}
public void TestAction_2_3()
{
    //3.使用Lambda表达式
    Action<int, int> powerAction = (baseNum, exponent) =>
    {
        int result = (int)Math.Pow(baseNum, exponent);
        Console.WriteLine($"{baseNum}^{exponent} = {result}");
    };
    //使用
    powerAction(2, 3);
}
public void TestAction_2_4()
{
    //4.多播委托
    Action multiAction = () => Console.WriteLine("First action");
    multiAction += () => Console.WriteLine("Second action");

    multiAction();
}
在这里插入代码片

Action 与 Func 的区别:Action用于没有返回值的方法 ; Func用于有返回值的方法;

 //Action与Func的区别:Action用于没有返回值的方法   ; Func用于有返回值的方法;
 public void TestAction_3_1()
 {
     Func<int,int,int> addFunc = (a, b) =>a + b;
     int result = addFunc(1, 2);
 }

实际应用示例:

public void TestAction_3_1()
{
    //实际应用实例1:回调机制
    void DownloadData(string url, Action<string> callback)
    {
        Console.WriteLine($"Downloading data from {url}...");
        // 模拟下载完成
        string data = "Sample data";
        callback(data);
    }

    // 使用
    DownloadData("http://example.com", data =>
    {
        Console.WriteLine($"Data received: {data}");
    });
}
public void TestAction_3_2()
{
    //实际应用实例2:延迟执行
    void ExecuteAfterDelay(int delayMs, Action action)
    {
        Thread.Sleep(delayMs);
        action();
    }

    ExecuteAfterDelay(2000, () => Console.WriteLine("Executed after 2 seconds"));
}
public void TestAction_3_3()
{
    //实际应用实例3:条件执行
    void ExecuteIf(bool condition, Action action)
    {
        if (condition)
        {
            action();
        }
        else
        {
            Console.WriteLine("Action not executed");
        }
    }

    ExecuteIf(true, () => Console.WriteLine("This will execute"));
    ExecuteIf(false, () => Console.WriteLine("This won't execute"));
}

7.ref关键字

在 C# 中用于传递参数的引用而不是值,允许方法修改调用者传递的变量。在使用时加入ref关键字

基本用法:
//方法1.按引用传递参数
void ModifyValue(ref int x)
{
    x = x * 2;
}
//方法1调用
int number = 5;
ModifyValue(ref number);
Console.WriteLine(number); // 输出 10

//方法2.引用返回值(C# 7.0)
ref int Find(int[] numbers, int value)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == value)
        {
            return ref numbers[i];
        }
    }
    throw new AggregateException("value not found");
}

//方法2调用:
//调用2
int[] array = {1,3,4,5};
ref int item = ref Find(array, 3);
item = 10;  //直接修改数组中的元素
Console.WriteLine(string.Join(",",array));  //输出1,10,4,5
//3.引用局部变量(C# 7.0)
int a = 5;
ref int b = ref a;      //b是a的引用
b = 10;
Console.WriteLine(a);   //输出10

实际应用示例:

//应用1:交换两个变量的值
void Swap(ref int x, ref int y)
{
    int temp = x;
    x = y;
    y = temp;
}

//调用:
//应用1 -使用
int a = 10; int b = 20; 
Swap(ref a, ref b);
Console.WriteLine($"a = {a},b={b}");    //输出a = 20,b=10
//应用2:修改数组元素
void IncrementArrayElements(ref int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i]++;
    }
}

//应用2 -使用
int[] numbers= { 1,2, 3 };
IncrementArrayElements(ref numbers);
Console.WriteLine(string.Join(",",numbers));        //输出2,3,4

ref和out对比:
ref:调用前必须初始化;可以读取或修改;双向传递数据;
out:调用前无需初始化;必须在返回前赋值;主要用于输出数据;

8.out关键字

out关键字在 C# 中用于传递参数的引用,主要用于从方法中返回多个值。
基础用法:

 //out基本用法1:声明out参数
 void GetValues(out int x, out string y)
 {
     x = 10;         //必须在方法内赋值
     y = "hello";    //必须在方法内赋值
 }

//out基本用法1:调用
int a;
string b;
GetValues(out a, out b);
Console.WriteLine($"{a} ,{b}");
out基本用法2:方法调用时使用 out
///从C#7.0开始,可以在调用时直接声明out变量
///
GetValues(out int aa, out string bb);
Console.WriteLine($"{aa},{bb}");        //输出"10,hello"

out实际应用示例

//out实际应用示例1:TryParse模式[格式转换的一种形式]
if(int.TryParse("123",out int result))
{
    Console.WriteLine($"转换成功:{result}");
}
else
{
    Console.WriteLine("转换失败");
}
//out实际应用示例2:返回多个值
void GetCircleProperties(double radius, out double area, out double circumference)
{
    area = Math.PI * radius * radius;
    circumference = 2 * Math.PI * radius;
}

//out实际应用示例2:返回多个值,调用
GetCircleProperties(5, out double a, out double c);
Console.WriteLine($"面积: {a}, 周长: {c}");
//out实际应用示例3:交换变量值
void Swap(out int x, out int y, int a, int b)
{
    x = b;
    y = a;
}
//out实际应用示例3:交换变量值,调用
int first = 10, second = 20;
Swap(out first, out second, first, second);
Console.WriteLine($"first: {first}, second: {second}"); // first: 20, second: 10

高级用法:

//Out高级用法1:接口中的out参数(协变)
public interface Interface_TestOut<T>
{
    bool TryProcess(out T result);

}

//Out高级用法1:接口中的out参数(协变)
public class TestOutItf_Class : Interface_TestOut<string>
{
    public bool TryProcess(out string result)
    {
        result = "Processed";
        return true;
    }
}

//out高级用法2:元组与out结合
var dict = new Dictionary<string, int> { ["one"] = 1, ["two"] = 2};

if(dict.TryGetValue("one",out var value))
{
    Console.WriteLine(value);

}

9.static关键字

主要用于创建类级别的成员而不是实例级别的成员
静态类【静态类不能被实例化,只能包含静态成员】
静态字段【静态字段属于类而不是实例,所有实例共享同一个静态字段】
静态方法【静态方法属于类而不是实例,不能访问实例成员】
静态构造函数【用于初始化静态字段,在类第一次被使用前自动调用】
静态属性【可使用类名.属性进行读取与修改】
静态成员常用于工具类、共享数据、单例模式等场景,可以避免不必要的对象实例化,提高性能。

10.索引器this关键字

它允许对象像数组一样通过索引来访问。索引器类似于属性,但使用索引而不是属性名来访问。
索引器提供了一种方便的方式来访问类或结构中的数据,使得对象可以像数组或集合一样被访问,提高了代码的可读性和易用性

简单示例:

class StringArray
{
    private string[] array = new string[5];
    
    // 索引器定义
    public string this[int index]
    {
        get
        {
            if (index < 0 || index >= array.Length)
                throw new IndexOutOfRangeException();
            return array[index];
        }
        set
        {
            if (index < 0 || index >= array.Length)
                throw new IndexOutOfRangeException();
            array[index] = value;
        }
    }
}

// 使用示例
StringArray sa = new StringArray();
sa[0] = "Hello";  // 调用set访问器
Console.WriteLine(sa[0]);  // 调用get访问器,输出"Hello"

可以使用除开整数类型的其他类型的索引(例如字符串)

class Dictionary
{
    private Dictionary<string, string> dict = new Dictionary<string, string>();
    
    public string this[string key]
    {
        get { return dict[key]; }
        set { dict[key] = value; }
    }
}

// 使用示例
Dictionary d = new Dictionary();
d["name"] = "John";
Console.WriteLine(d["name"]);  // 输出"John"

使用多个索引(类似二维数组)

class Matrix
{
    private int[,] data = new int[3, 3];
    
    public int this[int row, int col]
    {
        get { return data[row, col]; }
        set { data[row, col] = value; }
    }
}

// 使用示例
Matrix m = new Matrix();
m[0, 0] = 1;
m[1, 1] = 2;
Console.WriteLine(m[0, 0]);  // 输出1

索引器与属性的区别:1.索引器使用this关键字定义,属性使用名称定义。2.

11.var关键字

var是 C# 3.0 引入的一个隐式类型关键字,它允许编译器根据初始化表达式自动推断变量的类型。
使用场景1:简化复杂类型声明

// 不使用 var
Dictionary<string, List<Employee>> employeeGroups = new Dictionary<string, List<Employee>>();

// 使用 var
var employeeGroups = new Dictionary<string, List<Employee>>();
var person = new { Name = "Alice", Age = 25 };
Console.WriteLine($"{person.Name} is {person.Age} years old");

需要注意:使用 var 时必须同时初始化变量;不能用于字段:只能在方法内部使用;

// 方法返回值
var stream = File.OpenRead("test.txt");

// foreach 循环
foreach (var item in collection) { ... }

11.字典的常用用法【C#字典常用用法及示例】

Dictionary<TKey, TValue> 是 C# 中常用的键值对集合,提供了高效的查找、添加和删除操作

//1. 创建空字典
Dictionary<string, int> ages = new Dictionary<string, int>();

//2. 添加
ages.Add("Alice", 95);  // 使用 Add 方法
ages["Bob"] = 88;       // 使用索引器添加

//3. 创建并初始化字典
Dictionary<string, string> capitals = new Dictionary<string, string>
{
    {"China", "Beijing"},
    {"USA", "Washington D.C."},
    {"Japan", "Tokyo"}
};

//4. 访问元素
//4.1使用索引器访问
string appleColor = colors["apple"];  // "red"

//4.2使用 TryGetValue 安全访问
if (colors.TryGetValue("banana", out string bananaColor))
{
    Console.WriteLine(bananaColor);  // "yellow"
}

//5. 检查键是否存在
if (students.ContainsKey(101))
{
    Console.WriteLine("Student 101 exists.");
}

//6.遍历
// 遍历键值对
foreach (KeyValuePair<string, decimal> item in prices)
{
    Console.WriteLine($"{item.Key}: ${item.Value}");
}
// 只遍历键
foreach (string key in prices.Keys)
{
    Console.WriteLine(key);
}
// 只遍历值
foreach (decimal value in prices.Values)
{
    Console.WriteLine(value);
}

//7.修改和删除元素
// 修改值
inventory["Apples"] = 45;
// 删除元素
inventory.Remove("Oranges");
// 清空字典
inventory.Clear();

12.List列表的常用用法【C#中List的用法及优点总结】

 创建一个空列表
List<int> numbers = new List<int>();

// 创建并初始化列表
List<string> fruits = new List<string>() { "Apple", "Banana", "Orange" };

//添加元素
fruits.Add("Red");  

//删除元素
numbers.Remove(4);         // 删除第一个出现的4
numbers.RemoveAt(0);       // 删除索引0的元素
numbers.RemoveAll(n => n > 3); // 删除所有大于3的元素
numbers.Clear();           // 清空整个列表

//查找元素
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

bool containsBob = names.Contains("Bob"); // true
int index = names.IndexOf("Charlie");     // 返回2
string found = names.Find(name => name.StartsWith("A")); // "Alice"

13.另外类的常用用法【转盘数据data】

流程中使用:

private DutData _dutData;	//在整个流程中定义

//获取当前数据:
_dutData = HandleDutDataFunc.GetDutData(HandleDutDataFunc.Station.Station1);
/// <summary>
/// 获取指定工站DutData
/// </summary>
/// <param name="station">工站名</param>
public static DutData GetDutData(Station station)
{
    return _dic[station];
}
private static Dictionary<Station, DutData> _dic = [];

public enum Station
{
    Station1,
    Station2,
    Station3,
    Station4
}

//修改字典内容:转盘转动之后挪动
public static void UpdateDutData(RunDirection runDirection)
{
    DutData temp = _dic[Station.Station1];

    if (runDirection == RunDirection.CommonDirection)
    {
        _dic[Station.Station1] = _dic[Station.Station4];
        _dic[Station.Station4] = _dic[Station.Station3];
        _dic[Station.Station3] = _dic[Station.Station2];
        _dic[Station.Station2] = temp;
    }
    else
    {
        _dic[Station.Station1] = _dic[Station.Station2];
        _dic[Station.Station2] = _dic[Station.Station3];
        _dic[Station.Station3] = _dic[Station.Station4];
        _dic[Station.Station4] = temp;
    }
}


/// <summary>
/// 为指定工站DutData赋值
/// </summary>
/// <param name="station">工站名</param>
/// <param name="dutData">数据</param>
public static void SetDutData(Station station, DutData dutData)
{
    _dic[station] = dutData;
}

struct 结构体 与 class类的区别
类型 值类型 (Value Type) 引用类型 (Reference Type)
内存分配 直接包含数据 包含对数据的引用
修改影响 修改副本不影响原始值 修改引用对象会影响所有引用

13.数据库

Entity Framework Core

using Microsoft.EntityFrameworkCore;

// 定义数据库上下文
public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=localhost;Database=TestDB;User Id=username;Password=password;");
    }
}

/ 确保数据库已创建
            context.Database.EnsureCreated();
            
            // 添加数据
            var customer = new Customer { Name = "Jane Smith", Email = "jane@example.com" };
            context.Customers.Add(customer);
            context.SaveChanges();
            Console.WriteLine("数据已添加");
            
            // 查询数据
            var customers = context.Customers.ToList();
            foreach (var c in customers)
            {
                Console.WriteLine($"ID: {c.Id}, Name: {c.Name}, Email: {c.Email}");
            }
            
            // 更新数据
            var customerToUpdate = context.Customers.First();
            customerToUpdate.Name = "Updated Name";
            context.SaveChanges();

14.phase【多线程用法】

List<Task> tasks = [];
foreach (var phase in _phases)
{
    tasks.Add(Task.Run(() =>
    {
        phase.WaitTaskDone(timeoutInMs);
    }));
}
Task.WaitAll(tasks.ToArray());

4.委托

MVVM

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值