0

0

.NET泛型解析

高洛峰

高洛峰

发布时间:2016-12-19 15:55:13

|

1688人浏览过

|

来源于php中文网

原创

【1】:泛型介绍

泛型是c#2.0中一个重要的新特性,泛型是clr和编程语言提供的一种特殊机制,它支持另一种形式的代码重用。泛型通常用与集合以及作用于集合的方法一起使用,当然也可以单独使用.

C#是一种强类型的语言,在泛型没有被提出之前,我们在使用集合的代码的时候,每次对其进行转换都需要隐式的强制转换,我们都知道所有对象的最终基类是object,我们在每次使用object的时候,无论是变换什么类型都要对其进行强制转换。
那么有了泛型之后,使用泛型我们就无需进行转换,因为泛型根据接收者传入的参数类型,直接将每个类型更改为所传入的参数类型.
一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。 创建您自己的泛型类时,需要特别注意以下事项:

将哪些类型通用化为类型参数。

通常,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。 但是,太多的通用化会使其他开发人员难以阅读或理解代码。

如果存在约束,应对类型参数应用什么约束

一条有用的规则是,应用尽可能最多的约束,但仍使您能够处理必须处理的类型。 例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。 这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。

是否将泛型行为分解为基类和子类。

由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。 请参见本主题后面有关从泛型基类继承的规则。

是否实现一个或多个泛型接口。

例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能必须实现一个接口,如 IComparable,其中 T 是您的类的类型。

 

【2】:泛型的表示方式

泛型可以为引用类型和值类型还有接口和委托,FCL( DLL程序集,包含了.NET框架下的各种DLL )类中定义了一个泛型列表,用来管理一个对象集合,如果我们想要使用这个泛型列表,可以在使用时指定具体数据类型。
泛型的表示为 “T” 如:List, T 表示一个未指定的数据类型,我们可以看一下FCL类中泛型的引用定义:
System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能。
创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡

 

【3】:泛型的好处

1 : 使代码更加的简洁,清晰

从前面我们也提到了,泛型具备可重用性 , 减少我们代码量, 使我们的程序更容易开发和维护,举例 :
实例 1 : 在从数据库中获取数据库的时候,我们经常会返回一个DataTable类型,然后将其转换为List集合. 那么如果没有泛型的话,我会一般会这样来做.

public ListGetTrainingUser(string userId)
        {
            DataTable dt =
                     SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,
                        @"
                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU
                        INNER JOIN [USER] AS U
                         ON U.ID = TU.USERID 
                        JOIN [TRAINING] AS T
                        ON T.ID = TU.TRAININGID
                        WHERE U.ID = '"+userId+"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0];
            return DataTableToList(dt);
        }
 
        private List DataTableToList(DataTabledt)
        {
            List list = new List();
            if(dt. Rows.Count > 0 )
            {
                foreach (DataRow row in dt .Rows)
                {
                    TrainingUser trainingUser = new TrainingUser();
                    if(row["UserId" ] != null)
                    {
                        trainingUser .UserId = row["UserId"].ToString();
                    }
                    if(row["TrainingId" ] != null)
                    {
                        trainingUser.TrainingId = row["TrainingId"].ToString();
                    }
                    list.Add(trainingUser);
                }
            }
            return list;
        }

在方法DataTableToList中,我们传入了一个DataTable的对象,然后在去循环遍历每一行的对象值从而去赋值给TrainingUser类型对象,这只是其中的一个方法,如果我们的类型还有 Training/User/Project等类型的话,我们是不是就要写很多如同DataTableToList这样的方法呢? 这就体现出了这样的方式,会造成代码的冗余以及维护不便问题,那么我们使用泛型来解决

实例 2 : 使用泛型使上面的代码更见的清晰,简洁

public static List ToList1(DataTable dt) whereT : class, new()
        {
            var prlist =new List();
            Type type = typeof(T);
            Array.ForEach(
                type.GetProperties(),
                p =>
                {
                    if(dt.Columns.IndexOf(p.Name) !=-1)
                    {
                        prlist.Add(p);
                    }
                });
            var oblist = new List();
 
            // System.Data.SqlTypes.
            foreach(DataRow row in dt.Rows)
            {
                var ob = new T();
                prlist.ForEach(
                    p =>
                    {
                        if(row[p.Name] != DBNull.Value)
                        {
                            p.SetValue(ob, row[p.Name], null);
                        }
                    });
                oblist.Add(ob);
            }
 
            return oblist;
        }

在上面的这个方法中,我们定义了一个泛型方法,内部实现中是使用了反射的原理,将DataTable转换为了List(反射后续随笔中总结,此处只关注泛型部分即可),我们定义了一个静态的返回值为List ,前面我们说过 T : 代表任意类型(枚举除外),ToList1,说明我们在调用这个方法的时候,同时要赋予方法名一个类型值,这个类型要和它的返回值类型一致(泛型是类型安全的),Where : 用于限制T的条件 ,例如 where T : class,new() 表示 T 只能是一个类,或者一个类型对象,那么我们在调用的时候就可以这样来

public ListGetTrainingIdByUserId(string userId)
        {
              List trainingUserList =  DataTableHelper.ToList1(
                    SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,
                        @"
                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU
                        INNER JOIN [USER] AS U
                         ON U.ID = TU.USERID 
                        JOIN [TRAINING] AS T
                        ON T.ID = TU.TRAININGID
                        WHERE U.ID = '"+ userId +"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0]);
              return trainingUserList ;
        }

代码中的DataTableHelper.ToList1 即为我们刚才所写的一个泛型方法,这样我们可以看到,ToList 传入的类型为TrainingUser,同时接收者为:List = List ,
这样即便我们后续还有Training/User/Project等其他的类型,我们都可以直接使用DataTableHelper.ToList1(DataTable dt) 来进行类型转换.

2 : 提升程序的性能

泛型与非泛型相比较而言,性能要好一些,这是为什么? 首先,泛型的类型是由调用者(接收者),去直接赋值的(类型安全的), 那么就不会存在类型转换的问题,其次, 泛型减少了装箱和拆箱的过程.
实例 3 : 对于值类型泛型与非泛型的性能比较

private static void ListTest()
        {
            Listlist = new List();
            for(inti = 0; i < 100; i++)
            {
                list.Add(i);
                int a = list[i];
            }
            list =null;
        }
        private static void ArrListTest()
        {
            ArrayList arr = new ArrayList();
            for(inti = 0; i <100; i++)
            {
                arr.Add(i);
                int s = (int)arr[i];
            }
            arr = null;
        }
 
             Stopwatch sw = new Stopwatch();
            sw.Start();
            ListTest();
            Console.WriteLine(" 使用泛型List执行值类型方法历时 :  "+ sw.Elapsed.ToString());
            sw.Stop();
 
            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            ArrListTest();
            Console.WriteLine(" 使用非泛型ArrayList执行值类型方法历时 :  "+ sw1.Elapsed.ToString());
            sw1.Stop();
            Console.ReadLine();

通过循环 100 来比较,结果为 :

.NET泛型解析

我们可以看到非泛型的时间要比泛型的时间多出0.0000523秒,泛型比非泛型的时间要多出一些, 那么我们将数值改动一下改为循环 1000次.得出结果为 :

.NET泛型解析

泛型比非泛型执行的时间要短0.0000405秒
我们将时间在改动一下,改为 100000呢?结果为 :

.NET泛型解析

这次差距为 0.0054621 并且随着执行次数的增长,非泛型相比泛型的时间会逐步的增加,
通过反编译我们也能看出 :
泛型:

.NET泛型解析

非泛型

.NET泛型解析

从编译中我们也能看出泛型方法中,接收的为Int32,非泛型为Object,其次泛型不会进行装箱和拆箱操作,非泛型每次执行都要进行装箱和拆箱操作.

3 : 类型安全

在实例1 , 2 ,3 中我们都有备注说明,泛型的发送着必须要和接收者进行一致,否则会报异常 ,例如 :

实例 4 :

.NET泛型解析

首页

最新文章

IT 职场

前端

后端

移动端

数据库

运维

其他技术

- 导航条 -首页最新文章IT 职场前端- JavaScript- HTML5- CSS后端- Python- Java- C/C++- PHP- .NET- Ruby- Go移动端- Android- iOS数据库运维- Linux- UNIX其他技术- Git- 机器学习- 算法- 测试- 信息安全- Vim

伯乐在线 > 首页 > 所有文章 > 开发 > .NET泛型解析(上)

.NET泛型解析(上)

2015/07/03 · 开发 · .Net

分享到:6

拍摄与剪辑“怀孕日记”

PS大神通关教程

PS入门基础-魔幻调色

慕课网技术沙龙之前端专场

原文出处: 刘彬的博客   

【1】:泛型介绍

Memories.ai
Memories.ai

专注于视频解析的AI视觉记忆模型

下载

泛型是c#2.0中一个重要的新特性,泛型是clr和编程语言提供的一种特殊机制,它支持另一种形式的代码重用。泛型通常用与集合以及作用于集合的方法一起使用,当然也可以单独使用.

C#是一种强类型的语言,在泛型没有被提出之前,我们在使用集合的代码的时候,每次对其进行转换都需要隐式的强制转换,我们都知道所有对象的最终基类是object,我们在每次使用object的时候,无论是变换什么类型都要对其进行强制转换。
那么有了泛型之后,使用泛型我们就无需进行转换,因为泛型根据接收者传入的参数类型,直接将每个类型更改为所传入的参数类型.
一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。 创建您自己的泛型类时,需要特别注意以下事项:

将哪些类型通用化为类型参数。

通常,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。 但是,太多的通用化会使其他开发人员难以阅读或理解代码。

如果存在约束,应对类型参数应用什么约束

一条有用的规则是,应用尽可能最多的约束,但仍使您能够处理必须处理的类型。 例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。 这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。

是否将泛型行为分解为基类和子类。

由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。 请参见本主题后面有关从泛型基类继承的规则。

是否实现一个或多个泛型接口。

例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能必须实现一个接口,如 IComparable,其中 T 是您的类的类型。

 

【2】:泛型的表示方式

泛型可以为引用类型和值类型还有接口和委托,FCL( DLL程序集,包含了.NET框架下的各种DLL )类中定义了一个泛型列表,用来管理一个对象集合,如果我们想要使用这个泛型列表,可以在使用时指定具体数据类型。
泛型的表示为 “T” 如:List, T 表示一个未指定的数据类型,我们可以看一下FCL类中泛型的引用定义:
System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能。
创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡

 

【3】:泛型的好处

1 : 使代码更加的简洁,清晰

从前面我们也提到了,泛型具备可重用性 , 减少我们代码量, 使我们的程序更容易开发和维护,举例 :
实例 1 : 在从数据库中获取数据库的时候,我们经常会返回一个DataTable类型,然后将其转换为List集合. 那么如果没有泛型的话,我会一般会这样来做.

C#

public ListGetTrainingUser(string userId)
        {
            DataTable dt =
                     SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,
                        @"
                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU
                        INNER JOIN [USER] AS U
                         ON U.ID = TU.USERID
                        JOIN [TRAINING] AS T
                        ON T.ID = TU.TRAININGID
                        WHERE U.ID = '"+userId+"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0];
            return DataTableToList(dt);
        }
 
        private List DataTableToList(DataTabledt)
        {
            List list = new List();
            if(dt. Rows.Count > 0 )
            {
                foreach (DataRow row in dt .Rows)
                {
                    TrainingUser trainingUser = new TrainingUser();
                    if(row["UserId" ] != null)
                    {
                        trainingUser .UserId = row["UserId"].ToString();
                    }
                    if(row["TrainingId" ] != null)
                    {
                        trainingUser.TrainingId = row["TrainingId"].ToString();
                    }
                    list.Add(trainingUser);
                }
            }
            return list;
        }

   

 

在方法DataTableToList中,我们传入了一个DataTable的对象,然后在去循环遍历每一行的对象值从而去赋值给TrainingUser类型对象,这只是其中的一个方法,如果我们的类型还有 Training/User/Project等类型的话,我们是不是就要写很多如同DataTableToList这样的方法呢? 这就体现出了这样的方式,会造成代码的冗余以及维护不便问题,那么我们使用泛型来解决

实例 2 : 使用泛型使上面的代码更见的清晰,简洁

C#

public static List ToList1(DataTable dt) whereT : class, new()
        {
            var prlist =new List();
            Type type = typeof(T);
            Array.ForEach(
                type.GetProperties(),
                p =>
                {
                    if(dt.Columns.IndexOf(p.Name) !=-1)
                    {
                        prlist.Add(p);
                    }
                });
            var oblist = new List();
 
            // System.Data.SqlTypes.
            foreach(DataRow row in dt.Rows)
            {
                var ob = new T();
                prlist.ForEach(
                    p =>
                    {
                        if(row[p.Name] != DBNull.Value)
                        {
                            p.SetValue(ob, row[p.Name], null);
                        }
                    });
                oblist.Add(ob);
            }
 
            return oblist;
        }

   

在上面的这个方法中,我们定义了一个泛型方法,内部实现中是使用了反射的原理,将DataTable转换为了List(反射后续随笔中总结,此处只关注泛型部分即可),我们定义了一个静态的返回值为List ,前面我们说过 T : 代表任意类型(枚举除外),ToList1,说明我们在调用这个方法的时候,同时要赋予方法名一个类型值,这个类型要和它的返回值类型一致(泛型是类型安全的),Where : 用于限制T的条件 ,例如 where T : class,new() 表示 T 只能是一个类,或者一个类型对象,那么我们在调用的时候就可以这样来

C#

public ListGetTrainingIdByUserId(string userId)
        {
              List trainingUserList =  DataTableHelper.ToList1(
                    SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,
                        @"
                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU
                        INNER JOIN [USER] AS U
                         ON U.ID = TU.USERID
                        JOIN [TRAINING] AS T
                        ON T.ID = TU.TRAININGID
                        WHERE U.ID = '"+ userId +"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0]);
              return trainingUserList ;
        }

   

代码中的DataTableHelper.ToList1 即为我们刚才所写的一个泛型方法,这样我们可以看到,ToList 传入的类型为TrainingUser,同时接收者为:List = List ,
这样即便我们后续还有Training/User/Project等其他的类型,我们都可以直接使用DataTableHelper.ToList1(DataTable dt) 来进行类型转换.

2 : 提升程序的性能

泛型与非泛型相比较而言,性能要好一些,这是为什么? 首先,泛型的类型是由调用者(接收者),去直接赋值的(类型安全的), 那么就不会存在类型转换的问题,其次, 泛型减少了装箱和拆箱的过程.
实例 3 : 对于值类型泛型与非泛型的性能比较

C#

private static void ListTest()
        {
            Listlist = new List();
            for(inti = 0; i < 100; i++)
            {
                list.Add(i);
                int a = list[i];
            }
            list =null;
        }
        private static void ArrListTest()
        {
            ArrayList arr = new ArrayList();
            for(inti = 0; i <100; i++)
            {
                arr.Add(i);
                int s = (int)arr[i];
            }
            arr = null;
        }
 
             Stopwatch sw = new Stopwatch();
            sw.Start();
            ListTest();
            Console.WriteLine(" 使用泛型List执行值类型方法历时 :  "+ sw.Elapsed.ToString());
            sw.Stop();
 
            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            ArrListTest();
            Console.WriteLine(" 使用非泛型ArrayList执行值类型方法历时 :  "+ sw1.Elapsed.ToString());
            sw1.Stop();
            Console.ReadLine();

   

通过循环 100 来比较,结果为 :

我们可以看到非泛型的时间要比泛型的时间多出0.0000523秒,泛型比非泛型的时间要多出一些, 那么我们将数值改动一下改为循环 1000次.得出结果为 :

泛型比非泛型执行的时间要短0.0000405秒
我们将时间在改动一下,改为 100000呢?结果为 :

这次差距为 0.0054621 并且随着执行次数的增长,非泛型相比泛型的时间会逐步的增加,
通过反编译我们也能看出 :
泛型:

非泛型

 

从编译中我们也能看出泛型方法中,接收的为Int32,非泛型为Object,其次泛型不会进行装箱和拆箱操作,非泛型每次执行都要进行装箱和拆箱操作.

3 : 类型安全

在实例1 , 2 ,3 中我们都有备注说明,泛型的发送着必须要和接收者进行一致,否则会报异常 ,例如 :

实例 4 :

将一个泛型算法应用于一个具体的类型时,编译器和CLR能理解开发人员的意图,并保证只有与指定数据类型兼容的对象才能随同算法使用,若试图使用不兼容类型的一个对象,会造成编译时错误,或者运行时抛出异常

更多.NET泛型解析相关文章请关注PHP中文网!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

28

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

8

2026.01.26

苹果官方查询网站 苹果手机正品激活查询入口
苹果官方查询网站 苹果手机正品激活查询入口

苹果官方查询网站主要通过 checkcoverage.apple.com/cn/zh/ 进行,可用于查询序列号(SN)对应的保修状态、激活日期及技术支持服务。此外,查找丢失设备请使用 iCloud.com/find,购买信息与物流可访问 Apple (中国大陆) 订单状态页面。

31

2026.01.26

npd人格什么意思 npd人格有什么特征
npd人格什么意思 npd人格有什么特征

NPD(Narcissistic Personality Disorder)即自恋型人格障碍,是一种心理健康问题,特点是极度夸大自我重要性、需要过度赞美与关注,同时极度缺乏共情能力,背后常掩藏着低自尊和不安全感,影响人际关系、工作和生活,通常在青少年时期开始显现,需由专业人士诊断。

3

2026.01.26

windows安全中心怎么关闭 windows安全中心怎么执行操作
windows安全中心怎么关闭 windows安全中心怎么执行操作

关闭Windows安全中心(Windows Defender)可通过系统设置暂时关闭,或使用组策略/注册表永久关闭。最简单的方法是:进入设置 > 隐私和安全性 > Windows安全中心 > 病毒和威胁防护 > 管理设置,将实时保护等选项关闭。

5

2026.01.26

2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】
2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】

铁路12306提供起售时间查询、起售提醒、购票预填、候补购票及误购限时免费退票五项服务,并强调官方渠道唯一性与信息安全。

35

2026.01.26

个人所得税税率表2026 个人所得税率最新税率表
个人所得税税率表2026 个人所得税率最新税率表

以工资薪金所得为例,应纳税额 = 应纳税所得额 × 税率 - 速算扣除数。应纳税所得额 = 月度收入 - 5000 元 - 专项扣除 - 专项附加扣除 - 依法确定的其他扣除。假设某员工月工资 10000 元,专项扣除 1000 元,专项附加扣除 2000 元,当月应纳税所得额为 10000 - 5000 - 1000 - 2000 = 2000 元,对应税率为 3%,速算扣除数为 0,则当月应纳税额为 2000×3% = 60 元。

12

2026.01.26

oppo云服务官网登录入口 oppo云服务登录手机版
oppo云服务官网登录入口 oppo云服务登录手机版

oppo云服务https://cloud.oppo.com/可以在云端安全存储您的照片、视频、联系人、便签等重要数据。当您的手机数据意外丢失或者需要更换手机时,可以随时将这些存储在云端的数据快速恢复到手机中。

40

2026.01.26

抖币充值官方网站 抖币性价比充值链接地址
抖币充值官方网站 抖币性价比充值链接地址

网页端充值步骤:打开浏览器,输入https://www.douyin.com,登录账号;点击右上角头像,选择“钱包”;进入“充值中心”,操作和APP端一致。注意:切勿通过第三方链接、二维码充值,谨防受骗

7

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Django 教程
Django 教程

共28课时 | 3.5万人学习

Go 教程
Go 教程

共32课时 | 4.2万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号