为什么要使用using System.Collections.Generic;?

泛型集合类详解
本文深入探讨了.NET框架中的泛型集合类,如List<T>和Dictionary<K,V>的使用方法,包括排序、搜索和初始化过程。通过具体示例展示了如何利用泛型集合提高代码效率和类型安全性。
部署运行你感兴趣的模型镜像

 

实际上,本书前面的每个应用程序都有如下命名空间:

using System;

using System.Collections.Generic;

using System.Text;

System命名空间包含.NET应用程序使用的大多数基本类型。System.Text命名空间包含与字符串处理和编码相关的类型,但System.Collections.Generic命名空间包含什么类型?为什么要在默认情况下把它包含在控制台应用程序中?

这个命名空间包含用于处理集合的泛型类型,使用得非常频繁,用using语句配置它,使用起来就不必添加限定符了。

如本章前面所述,下面就介绍这些泛型类型,它们可以使工作更容易完成,可以毫不费力地创建强类型化的集合类。表12-2描述了本节要介绍的类型,本章后面还会详细阐述这些类型。

表 12-2

类 型
说 明

List<T>
T类型对象的集合

Dictionary<K, V>
V类型的项与K类型的键值相关的集合

后面还会介绍和这些类一起使用的各种接口和委托。

1. List<T>
使用这个泛型的集合类型会更快捷、更简单,而不是像上一章那样,从CollectionBase中派生一个类,实现需要的方法。它的另一个好处是正常情况下需要实现的许多方法(例如Add())已经自动实现了。

创建T类型对象的集合需要如下代码:

List<T> myCollection = new List<T>();

这就足够了。没有定义类、实现方法和进行其他操作。还可以把List<T>对象传送给构造函数,在集合中设置项的起始列表。

使用这个语法实例化的对象将支持表12-3中的方法和属性(其中,提供给List<T>泛型的类型是T)。

表 12-3

成 员
说 明

int Count
该属性给出集合中项的个数

void Add(T item)
把item添加到集合中

void AddRange(IEnumerable<T>)
把多个项添加到集合中

IList<T> AsReadOnly()
给集合返回一个只读接口

int Capacity
获取或设置集合可以包含的项数

void Clear()
删除集合中的所有项

bool Contains(T item)
确定item是否包含在集合中

void CopyTo(T[] array, int index)
把集合中的项复制到数组array中,从数组的索引index开始

IEnumerator<T> GetEnumerator()
获取一个IEnumerator<T>实例,用于迭代集合。注意返回的接口强类型化为T,所以在foreach循环中不需要类型转换

int IndexOf(T item)
获取item的索引,如果项没有包含在集合中,就返回-1

void Insert(int index, T item)
把item插入到集合的指定索引上

bool Remove(T item)
从集合中删除第一个item,并返回true;如果item不包含在集合中,就返回false

void RemoveAt(int index)
从集合中删除索引index处的项

List<T>还有一个Item属性,可以进行类似于数组的访问,如下所示:

T itemAtIndex2 = myCollectionOfT[2];

这个类还支持其他几个方法,但上述知识已足以开始使用该类了。

下面的示例介绍如何使用Collection<T>。

试试看:使用Collection<T>

(1) 在目录C:/BegVCSharp/Chapter12下创建一个新控制台应用程序项目Ch12Ex02。

(2) 在Solution Explorer窗口中右击项目名称,选择Add | Add Existing Item...选项。

(3) 在C:/BegVCSharp/Chapter11/Ch11Ex01/Ch11Ex01目录下选择Animal.cs、Cow.cs和Chicken.cs文件,单击Add。

(4) 修改这3个文件中的命名空间声明,如下所示:

namespace Ch12Ex02

(5) 修改Program.cs中的代码,如下所示:

static void Main(string[] args)

{

List<Animal> animalCollection = new List<Animal>();

animalCollection.Add(new Cow("Jack"));

animalCollection.Add(new Chicken("Vera"));

foreach (Animal myAnimal in animalCollection)

{

myAnimal.Feed();

}

Console.ReadKey();

}

(6) 执行应用程序,结果与上一章的Ch11Ex02相同。

示例的说明

这个示例与Ch11Ex02只有两个区别。第一个区别是下面的代码:

Animals animalCollection = new Animals();

被替换为:

List<Animal> animalCollection = new List<Animal>();

第二个区别比较重要:项目中不再有Animals集合类。前面为创建这个类所做的工作现在用一行代码即可完成,即使用泛型的集合类。

获得相同效果的另一个方法是不修改Program.cs中的代码,使用Animals的如下定义:

public class Animals : List<Animal>

{

}

这么做的优点是,能比较容易看懂Program.cs中的代码,还可以在合适时给Animals类添加额外的成员。

为什么不从CollectionBase中派生类?这是一个很好的问题。实际上,在许多情况下,我们都不会从CollectionBase中派生类。知道内部工作原理肯定是件好事,因为List<T>以相同的方式工作,但CollectionBase是向后兼容的。使用CollectionBase的惟一场合是要更多地控制向类的用户展示的成员。如果希望集合类的Add()方法使用内部访问修饰符,则使用CollectionBase是最佳选择。

注意:

也可以把要使用的初始容量(作为int)传递给List<T>的构造函数,或者传递使用IEnumerable<T>接口的初始项列表。支持这个接口的类包括List<T>。

2. 对泛型列表进行排序和搜索
给泛型列表进行排序与对其他列表进行排序是一样的。在上一章中,介绍了如何使用IComparer和IComparable接口比较两个对象,然后对该类型的对象列表排序。这里惟一的区别是,可以使用泛型接口IComparer<T>和IComparable<T>,它们略有区别、且针对特定类型的方法。表12-4列出了它们的区别。

表 12-4

泛 型 方 法
非泛型方法
区 别

int IComparable<T>.

CompareTo(T otherObj)
int IComparable.

CompareTo( object, otherObj)
泛型版本中是强类型化的

bool IComparable<T>.

Equals(T otherObj)
N/A
在非泛型接口中不存在,可以使用object.Equals()替代

int IComparer<T>.

Compare(T objectA, T objectB)
int IComparer.

Compare(object objectA, object objectB)
泛型版本中是强类型化的

bool IComparer<T>.

Equals(T objectA, T objectB)
N/A
在非泛型接口中不存在,可以使用object.Equals()替代

int IComparer<T>. GetHashCode (T objectA)
N/A
在非泛型接口中不存在,可以使用object. GetHashCode()替代

要对List<T>排序,可以在要排序的类型上提供IComparable<T>接口,或者提供IComparer<T>接口。另外,还可以提供泛型委托,作为排序方法。从了解工作原理的角度来看,这非常有趣,因为实现上述接口并不比实现其非泛型版本更麻烦。

一般情况下,给列表排序需要一个方法,来比较T类型的两个对象。要在列表中搜索,也需要一个方法来检查T类型的对象,看看它是否满足某个条件。定义这样的方法很简单,这里给出两个可以使用的泛型委托:

● Comparison<T>:这个委托类型用于排序方法,其签名是int method (T objectA, T objectB)。

● Predicate<T>:这个委托类型用于搜索方法,其签名是bool method (T targetObject)。

可以定义任意个这样的方法,使用它们实现List<T>的搜索和排序方法。下面的示例进行了演示。

试试看:List<T>的搜索和排序

(1) 在目录C:/BegVCSharp/Chapter12下创建一个新控制台应用程序项目Ch12Ex03。

(2) 在Solution Explorer窗口中右击项目名称,选择Add | Add Existing Item...选项。

(3) 在C:/BegVCSharp/Chapter12/Ch12Ex01/Ch12Ex01目录下选择Vector.cs文件,单击Add。

(4) 修改这个文件中的命名空间声明,如下所示:

namespace Ch12Ex03

(5) 添加一个新类Vectors。

(6) 修改Vectors.cs中的代码,如下所示:

public class Vectors : List<Vector>

{

public Vectors()

{

}

public Vectors(IEnumerable<Vector> initialItems)

{

foreach (Vector vector in initialItems)

{

Add(vector);

}

}

public string Sum()

{

StringBuilder sb = new StringBuilder();

Vector currentPoint = new Vector(0.0, 0.0);

sb.Append("origin");

foreach (Vector vector in this)

{

sb.AppendFormat(" + {0}", vector);

currentPoint += vector;

}

sb.AppendFormat(" = {0}", currentPoint);

return sb.ToString();

}

}

(7) 添加一个新类VectorDelegates。

(8) 修改VectorDelegates.cs中的代码,如下所示:

public static class VectorDelegates

{

public static int Compare(Vector x, Vector y)

{

if (x.R > y.R)

{

return 1;

}

else if (x.R < y.R)

{

return -1;

}

return 0;

}

public static bool TopRightQuadrant(Vector target)

{

if (target.Theta >= 0.0 && target.Theta <= 90.0)

{

return true;

}

else

{

return false;

}

}

}

(9) 修改Program.cs中的代码,如下所示:

static void Main(string[] args)

{

Vectors route = new Vectors();

route.Add(new Vector(2.0, 90.0));

route.Add(new Vector(1.0, 180.0));

route.Add(new Vector(0.5, 45.0));

route.Add(new Vector(2.5, 315.0));

Console.WriteLine(route.Sum());

Comparison<Vector> sorter = new Comparison<Vector>(VectorDelegates.Compare);

route.Sort(sorter);

Console.WriteLine(route.Sum());

Predicate<Vector> searcher =

new Predicate<Vector>(VectorDelegates.TopRightQuadrant);

Vectors topRightQuadrantRoute = new Vectors(route.FindAll(searcher));

Console.WriteLine(topRightQuadrantRoute.Sum());

Console.ReadKey();

}

(10) 执行应用程序,结果如图12-4所示。

 

图 12-4

示例的说明

在这个示例中,为Ch12Ex01中的Vector类创建了一个集合类Vectors。可以只使用List <Vector>类型的变量,但因为需要其他功能,所以使用了一个新类Vectors,它派生自List <Vector>,允许添加需要的其他成员。

该类有一个成员Sum(),依次返回每个矢量的字符串列表,并在最后把它们加在一起(使用源类Vector的重载+运算符)。每个矢量都可以看作“方向+距离”,所以这个矢量列表构成了一条有端点的路径。

public string Sum()

{

StringBuilder sb = new StringBuilder();

Vector currentPoint = new Vector(0.0, 0.0);

sb.Append("origin");

foreach (Vector vector in this)

{

sb.AppendFormat(" + {0}", vector);

currentPoint += vector;

}

sb.AppendFormat(" = {0}", currentPoint);

return sb.ToString();

}

这个方法使用System.Text命名空间中的StringBuilder类来构建响应字符串。这个类包含Append()和AppendFormat()等成员(这里使用),所以很容易构建字符串,其性能也高于连接各个字符串。使用这个类的ToString()方法即可获得最终的字符串。

本例还创建了两个用作委托的方法,作为VectorDelegates的静态成员。Compare()用于比较(排序),TopRightQuadrant()用于搜索。下面在讨论Program.cs中的代码时介绍它们。

Main()中的代码首先初始化Vectors集合,给它添加几个Vector对象:

Vectors route = new Vectors();

route.Add(new Vector(2.0, 90.0));

route.Add(new Vector(1.0, 180.0));

route.Add(new Vector(0.5, 45.0));

route.Add(new Vector(2.5, 315.0));

如前所述,Vectors.Sum()方法用于输出集合中的项,这次是按照其初始顺序输出:

Console.WriteLine(route.Sum());

接着,创建第一个委托sorter,这个委托是Comparison<Vector>类型的,因此可以赋予带如下签名的方法:

int method(Vector objectA, Vector objectB)

它匹配VectorDelegates.Compare(),该方法就是赋予委托的方法。

Comparison<Vector> sorter = new Comparison<Vector>(VectorDelegates.Compare);

Compare()比较两个矢量的大小,如下所示:

public static int Compare(Vector x, Vector y)

{

if (x.R > y.R)

{

return 1;

}

else if (x.R < y.R)

{

return -1;

}

return 0;

}

这样就可以按大小对矢量排序了:

route.Sort(sorter);

Console.WriteLine(route.Sum());

应用程序给出了我们期望的结果—— 汇总的结果是一样的,因为“矢量路径”的端点顺序与执行各个步骤的顺序相同。

然后,进行搜索,获取集合中的一个矢量子集。这需要使用VectorDelegates.TopRight Quadrant()来实现:

public static bool TopRightQuadrant(Vector target)

{

if (target.Theta >= 0.0 && target.Theta <= 90.0)

{

return true;

}

else

{

return false;

}

}

如果方法的Vector参数值是介于0到90°之间的Theta值,该方法就返回true,也就是说,它在前面的排序图中指向上或右。

在主函数体中,通过Predicate<Vector>类型的委托使用这个方法,如下所示:

Predicate<Vector> searcher =

new Predicate<Vector>(VectorDelegates.TopRightQuadrant);

Vectors topRightQuadrantRoute = new Vectors(route.FindAll(searcher));

Console.WriteLine(topRightQuadrantRoute.Sum());

这需要在Vectors中定义构造函数:

public Vectors(IEnumerable<Vector> initialItems)

{

foreach (Vector vector in initialItems)

{

Add(vector);

}

}

其中,使用IEnumerable<Vector>的实例初始化了一个新的Vectors集合,这是必须的,因为List<Vector>.FindAll()返回一个List<Vector>实例,而不是Vectors实例。

搜索的结果是,只返回Vector对象的一个子集,所以汇总的结果不同(这正是我们希望的)。

2. Dictionary<K, V>
这个类型可以定义键/值对的集合。与本章前面介绍的其他泛型集合类型不同,这个类需要实例化两个类型,分别用于键和值,以表示集合中的各个项。

实例化Dictionary<K, V>对象后,就可以对它执行与继承自DictionaryBase的类相同的一些操作,但要使用已有的类型安全的方法和属性。例如,可以使用强类型化的Add()方法添加键/值对。

Dictionary<string, int> things = new Dictionary<string, int>();

things.Add("Green Things", 29);

things.Add("Blue Things", 94);

things.Add("Yellow Things", 34);

things.Add("Red Things", 52);

things.Add("Brown Things", 27);

可以使用Keys和Values属性迭代集合中的键和值:

foreach (string key in things.Keys)

{

Console.WriteLine(key);

}

foreach (int value in things.Values)

{

Console.WriteLine(value);

}

还可以迭代集合中的各个项,把每个项作为一个KeyValuePair<K, V>实例来获取,这与上一章介绍的DictionaryEntry对象相同:

foreach (KeyValuePair<string, int> thing in things)

{

Console.WriteLine("{0} = {1}", thing.Key, thing.Value);

}

对于Dictionary<K, V>要注意的一点是,每个项的键都必须是惟一的。如果要添加的项的键与已有项的键相同,就会抛出ArgumentException异常。所以,Dictionary<K, V>允许把IComparer<K>接口传递给其构造函数,如果要把自己的类用作键,且它们不支持IComparable或IComparable<K>接口,或者要使用非默认的过程比较对象,就必须把IComparer<K>接口传递给其构造函数。例如,在上面的示例中,可以使用不区分大小写的方法比较字符串键:

Dictionary<string, int> things =

new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);

如果使用下面的键,就会得到一个异常:

things.Add("Green Things", 29);

things.Add("Green things", 94);

也可以给构造函数传递初始容量(使用int)或项的集合(使用IDictionary<K,V>接口)。

3. 修改CardLib,以使用泛型集合类
对前几章创建的CardLib项目可以进行简单的修改,即修改Cards集合类,以使用一个泛型集合类,这将减少许多代码。对Cards的类定义需要进行如下修改:

public class Cards : List<Card>, ICloneable

{

...

}

还可以删除Cards的所有方法,但Clone()和CopyTo()除外,因为Clone()是ICloneable需要的方法,而List<Card>提供的CopyTo()版本处理的是Card对象数组,而不是Cards集合。

这里没有列出代码,因为这是很简单的修改,CardLib的更新版本为Ch12CardLib,它和上一章的客户代码包含在本章的下载代码中。


本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/fredlau/archive/2007/12/17/1943095.aspx

 

您可能感兴趣的与本文相关的镜像

Qwen-Image

Qwen-Image

图片生成
Qwen

Qwen-Image是阿里云通义千问团队于2025年8月发布的亿参数图像生成基础模型,其最大亮点是强大的复杂文本渲染和精确图像编辑能力,能够生成包含多行、段落级中英文文本的高保真图像

<think>我们正在讨论C#中`using System.Collections.Generic;`的作用。根据用户提供的引用,我们可以总结如下: 引用[1]提到:`System.Collections.Generic`命名空间包含泛型集合类型,而`System`命名空间包含基本类型,`System.Text`包含字符串处理和编码相关类型。 引用[2]说明:我们可以使用`System.Collections.Generic`命名空间下的`List<T>`类来创建泛型集合,语法为`List<T> ListOfT = new List<T>();`。 引用[3]举例:在Unity中,如果不使用`using System.Collections.Generic;`,那么在代码中使用`List<T>`等泛型集合会导致编译错误。 引用[4]解释了非泛型集合的继承关系,但我们的重点是泛型集合。 因此,`using System.Collections.Generic;`的主要作用是引入C#中的泛型集合类型,如`List<T>`, `Dictionary<TKey, TValue>`, `HashSet<T>`等。这些类型在`System.Collections.Generic`命名空间中定义。 下面详细说明: 1. **为什么需要泛型集合?** - 在C# 2.0之前,集合(如`ArrayList`)使用`object`来存储元素,这导致: - 值类型需要装箱和拆箱,性能损失。 - 类型不安全,因为任何类型都可以放入集合,取出时需要强制类型转换,可能引发运行时异常。 - 泛型集合通过类型参数解决了上述问题: - 编译时类型检查,避免运行时类型错误。 - 无需装箱拆箱,提高性能。 - 代码更简洁(无需强制转换)。 2. **常用泛型集合类型**(在`System.Collections.Generic`命名空间中): - `List<T>`:动态数组,类似`ArrayList`,但类型安全。 - `Dictionary<TKey, TValue>`:键值对集合,类似`Hashtable`,但类型安全。 - `Queue<T>`:先进先出队列。 - `Stack<T>`:后进先出栈。 - `LinkedList<T>`:双向链表。 - `HashSet<T>`:不包含重复元素的集合。 3. **`using System.Collections.Generic;`的作用**: - 允许在代码中直接使用这些泛型集合类型,而无需每次都写完全限定名(如`System.Collections.Generic.List<T>`)。 - 简化代码,提高可读性。 4. **示例对比**: - 不使用`using`: ```csharp System.Collections.Generic.List<int> numbers = new System.Collections.Generic.List<int>(); ``` - 使用`using`: ```csharp using System.Collections.Generic; ... List<int> numbers = new List<int>(); ``` 5. **在控制台应用程序中默认包含的原因**: - 泛型集合是C#中最常用的数据结构之一,几乎每个程序都会使用到(如`List<T>`)。因此,在创建控制台应用程序时,Visual Studio默认添加了这个`using`指令,以方便开发者。 6. **与`System.Collections`(非泛型集合)的区别**: - `System.Collections`包含非泛型集合(如`ArrayList`, `Hashtable`),这些集合存储`object`类型元素。 - `System.Collections.Generic`包含泛型集合,提供类型安全和性能优势。 7. **代码示例**: ```csharp using System; using System.Collections.Generic; class Program { static void Main() { // 使用List<T> List<string> names = new List<string>(); names.Add("Alice"); names.Add("Bob"); // 使用Dictionary<TKey, TValue> Dictionary<int, string> employees = new Dictionary<int, string>(); employees.Add(1, "Alice"); employees.Add(2, "Bob"); // 遍历List foreach (string name in names) { Console.WriteLine(name); } } } ``` 总结:`using System.Collections.Generic;`指令允许我们在代码中直接使用泛型集合类型,这些类型提供了类型安全和性能优势,是C#编程中不可或缺的一部分[^1][^2][^3]。 §§相关问题§§ 1. C#中泛型集合与非泛型集合的主要区别是什么? 2. 如何在C#中使用`Dictionary<TKey, TValue>`存储和检索数据? 3. 为什么泛型集合在性能上优于非泛型集合?</think># C#中`using System.Collections.Generic`的作用详解 `using System.Collections.Generic;`是C#中用于引入泛型集合相关类型的命名空间指令,它在现代C#开发中扮演着关键角色。 ## 核心作用 ### 1. 提供类型安全的泛型集合 - **非泛型集合的问题**:传统集合(如`ArrayList`)存储`object`类型元素,需要频繁装箱拆箱,存在类型安全风险 - **泛型集合的优势**:`System.Collections.Generic`提供了类型安全的集合类,如: - `List<T>`:动态数组 - `Dictionary<TKey, TValue>`:键值对集合 - `Queue<T>`:先进先出队列 - `Stack<T>`:后进先出栈 - `HashSet<T>`:不重复元素集合 ### 2. 避免装箱拆箱开销 泛型集合直接操作具体类型,无需将值类型转换为`object`: ```csharp // 非泛型集合(需要装箱拆箱) ArrayList list = new ArrayList(); list.Add(42); // 装箱 int value = (int)list[0]; // 拆箱 // 泛型集合(无开销) List<int> genericList = new List<int>(); genericList.Add(42); // 直接存储int int val = genericList[0]; // 直接访问 ``` ### 3. 增强代码可读性和安全性 - 编译时类型检查:避免运行时类型转换错误 - 代码自文档化:集合类型直接表明元素类型 ## 关键类型解析 | 类型 | 描述 | 示例 | |------|------|------| | `List<T>` | 动态大小数组 | `List<string> names = new List<string>();` | | `Dictionary<TKey, TValue>` | 键值对集合 | `Dictionary<int, string> employees = new Dictionary<int, string>();` | | `Queue<T>` | 先进先出队列 | `Queue<Process> processes = new Queue<Process>();` | | `Stack<T>` | 后进先出栈 | `Stack<Command> undoStack = new Stack<Command>();` | | `HashSet<T>` | 唯一元素集合 | `HashSet<string> uniqueIds = new HashSet<string>();` | ## 使用场景示例 ### 创建和使用泛型集合 ```csharp using System.Collections.Generic; class Program { static void Main() { // 创建字符串列表 List<string> cities = new List<string> { "New York", "London", "Tokyo" }; // 添加元素 cities.Add("Paris"); // 安全访问(编译时类型检查) string firstCity = cities[0]; // 创建字典 Dictionary<int, string> products = new Dictionary<int, string> { {101, "Laptop"}, {102, "Smartphone"} }; // 安全检索 string productName = products[102]; // "Smartphone" } } ``` ## 与传统集合的对比 | 特性 | `System.Collections` (非泛型) | `System.Collections.Generic` (泛型) | |------|-------------------------------|-------------------------------------| | 类型安全 | ❌ 需要强制转换 | ✅ 编译时类型检查 | | 值类型处理 | ❌ 需要装箱拆箱 | ✅ 直接操作值类型 | | 性能 | ⚠️ 较低(装箱拆箱开销) | ✅ 高效 | | 代码清晰度 | ⚠️ 类型信息不明确 | ✅ 类型信息明确 | ## 为什么默认包含在控制台应用中? 1. **泛型集合使用频率高**:几乎所有程序都需要集合操作 2. **性能优势**:比传统集合更高效 3. **现代编程标准**:泛型是C#的核心特性之一 4. **减少样板代码**:避免重复编写类型转换逻辑 ## 实际应用建议 1. **优先使用泛型集合**:除非需要兼容旧代码 2. **明确指定类型参数**:避免使用`var`隐藏集合类型信息 3. **利用初始化语法**:简化集合创建和初始化 4. **结合LINQ使用**:实现强大的数据查询功能 ```csharp List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; var evenNumbers = numbers.Where(n => n % 2 == 0).ToList(); ``` 5. **注意空值处理**:使用`Nullable<T>`或`default`关键字处理可能为null的值 ## 常见问题解答 **Q: 什么时候需要手动添加这个using指令?** A: 当使用泛型集合类型时,如果Visual Studio未自动添加,或在新创建的类文件中使用这些集合。 **Q: 它会影响程序性能吗?** A: 不会。`using`指令只在编译时起作用,帮助编译器解析类型名称,不影响运行时性能。 **Q: 能否完全不使用这个命名空间?** A: 技术上可以,但需要使用完全限定名(如`System.Collections.Generic.List<T>`),这会显著降低代码可读性[^1][^2]。 `using System.Collections.Generic;`是现代C#开发的基础指令,它提供的泛型集合类型使代码更安全、高效和易维护,是每个C#开发者必须掌握的核心概念[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值