0

0

C#泛型方法解析

高洛峰

高洛峰

发布时间:2016-11-30 14:13:58

|

1725人浏览过

|

来源于php中文网

原创

    c#2.0引入了泛型这个特性,由于泛型的引入,在一定程度上极大的增强了c#的生命力,可以完成c#1.0时需要编写复杂代码才可以完成的一些功能。但是作为开发者,对于泛型可谓是又爱又恨,爱的是其强大的功能,以及该特性带来的效率的提升,恨的是泛型在复杂的时候,会呈现相当复杂的语法结构。这种复杂不仅是对于初学者,对于一些有开发经验的.net开发者,也是一个不那么容易掌握的特性。

   接下来我们来了解一下C#2.0加入的特性:泛型。

一.泛型的基本特性概述:

    在实际项目开发中,任何API只要将object作为参数类型和返回类型使用,就可能在某个时候涉及强类型转换。提到强类型转换,估计很多开发者第一反应就是“效率”这个次,对于强类型的利弊主要看使用者使用的环境,天底下没有绝对的坏事,也没有绝对的好事,有关强类型的问题不是本次的重点,不做重点介绍。

    泛型是CLR和C#提供的一种特殊机制,支持另一种形式的代码重用,即“算法重用”。泛型实现了类型和方法的参数化,泛型类型和方法也可以让参数告诉使用者使用什么类型。

    泛型所带来的好处:更好的编译时检查,更多在代码中能直接表现的信息,更多的IDE支持,更好的性能。可能有人会疑问,为什么泛型会带来这么多好处,使用一个不能区分不同类型的常规API,相当于在一个动态环境中访问那个API。

    CLR允许创建泛型引用和泛型值类型,但是不允许创建泛型枚举,并且CLR允许创建泛型接口和泛型委托,CLR允许在引用类型、值类型或接口中定义泛型方法。定义泛型类型或方法时,为类型指定了任何变量(如:T)都称为类型参数。(T是一个变量名,在源代码中能够使用一个数据类型的任何位置,都可以使用T)在C#中泛型参数变量要么成为T,要么至少一大写T开头。

二.泛型类、泛型接口和泛型委托概述:

   1.泛型类:

     泛型类型仍然是类型,所以可以从任何类型派生。使用一个泛型类型并指定类型实参时,实际是在CLR中定义一个新类型对象,新类型对象是从泛型派生自的那个类型派生的。使用泛型类型参数的一个方法在基尼险那个JIT编译时,CLR获取IL,用指定的类型实参进行替换,然后创建恰当的本地代码。

    如果没有为泛型类型参数提供类型实参,那就么就是未绑定泛型类型。如果指定了类型实参,该类型就是已构造类型。已构造类型可以是开发或封闭的,开发类型还包含一个类ixngcanshu,而封闭类型则不是开发的,类型的每个部分都是明确的。所有代码实际都是在一个封闭的已构造类型的上下文中执行。

   泛型类在.NET的应用主要在集合类中,大多数集合类在System.Collections.Generic和System.Collections.ObjectModel类中。下面简单的介绍一种泛型集合类:

     (1).SynchronizedCollection:提供一个线程安全集合,其中包含泛型参数所指定类型的对象作为元素.

[ComVisible(false)]
  public class SynchronizedCollection : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable
  {
    /// 
    /// 初始化  类的新实例。
    /// 
    public SynchronizedCollection();
    /// 
    /// 通过用于对线程安全集合的访问进行同步的对象来初始化  类的新实例。
    /// 
    /// 用于对线程安全集合的访问进行同步的对象。 为 null。
    public SynchronizedCollection(object syncRoot);
    /// 
    /// 使用指定的可枚举元素列表和用于对线程安全集合的访问进行同步的对象来初始化  类的新实例。
    /// 
    /// 用于对线程安全集合的访问进行同步的对象。用于初始化线程安全集合的元素的  集合。 为 null。
    public SynchronizedCollection(object syncRoot, IEnumerable list);
    /// 
    /// 使用指定的元素数组和用于对线程安全集合的访问进行同步的对象来初始化  类的新实例。
    /// 
    /// 用于对线程安全集合的访问进行同步的对象。用于初始化线程安全集合的  类型元素的  为 null。
    public SynchronizedCollection(object syncRoot, params T[] list);
    /// 
    /// 将项添加到线程安全只读集合中。
    /// 
    /// 要添加到集合的元素。设置的值为 null,或者不是集合的正确泛型类型 
    public void Add(T item);
    /// 
    /// 从集合中移除所有项。
    /// 
    public void Clear();
    /// 
    /// 从特定索引处开始,将集合中的元素复制到指定的数组。
    /// 
    /// 从集合中复制的 类型元素的目标 复制开始时所在的数组中的从零开始的索引。
    public void CopyTo(T[] array, int index);
    /// 
    /// 确定集合是否包含具有特定值的元素。
    /// 
    /// 
    /// 
    /// 如果在集合中找到元素值,则为 true;否则为 false。
    /// 
    /// 要在集合中定位的对象。设置的值为 null,或者不是集合的正确泛型类型 
    public bool Contains(T item);
    /// 
    /// 返回一个循环访问同步集合的枚举数。
    /// 
    /// 
    /// 
    /// 一个 ,用于访问集合中存储的类型的对象。
    /// 
    public IEnumerator GetEnumerator();
    /// 
    /// 返回某个值在集合中的第一个匹配项的索引。
    /// 
    /// 
    /// 
    /// 该值在集合中的第一个匹配项的从零开始的索引。
    /// 
    /// 从集合中移除所有项。设置的值为 null,或者不是集合的正确泛型类型 
    public int IndexOf(T item);
    /// 
    /// 将一项插入集合中的指定索引处。
    /// 
    /// 要从集合中检索的元素的从零开始的索引。要作为元素插入到集合中的对象。指定的  小于零或大于集合中的项数。设置的值为 null,或者不是集合的正确泛型类型 
    public void Insert(int index, T item);
    /// 
    /// 从集合中移除指定项的第一个匹配项。
    /// 
    /// 
    /// 
    /// 如果从集合中成功移除了项,则为 true;否则为 false。
    /// 
    /// 要从集合中移除的对象。
    public bool Remove(T item);
    /// 
    /// 从集合中移除指定索引处的项。
    /// 
    /// 要从集合中检索的元素的从零开始的索引。指定的  小于零或大于集合中的项数。
    public void RemoveAt(int index);
    /// 
    /// 从集合中移除所有项。
    /// 
    protected virtual void ClearItems();
    /// 
    /// 将一项插入集合中的指定索引处。
    /// 
    /// 集合中从零开始的索引,在此处插入对象。要插入到集合中的对象。指定的  小于零或大于集合中的项数。设置的值为 null,或者不是集合的正确泛型类型 
    protected virtual void InsertItem(int index, T item);
    /// 
    /// 从集合中移除指定  处的项。
    /// 
    /// 要从集合中检索的元素的从零开始的索引。指定的  小于零或大于集合中的项数。
    protected virtual void RemoveItem(int index);
    /// 
    /// 使用另一项替换指定索引处的项。
    /// 
    /// 要替换的对象的从零开始的索引。要替换的对象。指定的  小于零或大于集合中的项数。
    protected virtual void SetItem(int index, T item);
    /// 
    /// 返回一个循环访问同步集合的枚举数。
    /// 
    /// 
    /// 
    /// 一个 ,用于访问集合中存储的类型的对象。
    /// 
    IEnumerator IEnumerable.GetEnumerator();
    /// 
    /// 从特定索引处开始,将集合中的元素复制到指定的数组。
    /// 
    /// 从集合中复制的  类型元素的目标 复制开始时所在的数组中的从零开始的索引。
    void ICollection.CopyTo(Array array, int index);
    /// 
    /// 向集合中添加一个元素。
    /// 
    /// 
    /// 
    /// 新元素的插入位置。
    /// 
    /// 要添加到集合中的对象。
    int IList.Add(object value);
    /// 
    /// 确定集合是否包含具有特定值的元素。
    /// 
    /// 
    /// 
    /// 如果在集合中找到元素 ,则为 true;否则为 false。
    /// 
    /// 要在集合中定位的对象。 不是集合所含类型的对象。
    bool IList.Contains(object value);
    /// 
    /// 确定集合中某个元素的从零开始的索引。
    /// 
    /// 
    /// 
    /// 如果在集合中找到,则为  的索引;否则为 -1。
    /// 
    /// 集合中要确定其索引的元素。
    int IList.IndexOf(object value);
    /// 
    /// 将某个对象插入到集合中的指定索引处。
    /// 
    /// 从零开始的索引,将在该位置插入 要在集合中插入的对象。指定的  小于零或大于集合中的项数。设置的  为 null,或者不是集合的正确泛型类型 
    void IList.Insert(int index, object value);
    /// 
    /// 从集合中移除作为元素的指定对象的第一个匹配项。
    /// 
    /// 要从集合中移除的对象。
    void IList.Remove(object value);
   
  }

   (2).KeyedByTypeCollection:提供一个集合,该集合的项是用作键的类型。

[__DynamicallyInvokable]
  public class KeyedByTypeCollection : KeyedCollection
  {
    /// 
    /// 初始化  类的新实例。
    /// 
    public KeyedByTypeCollection();
    /// 
    /// 根据指定的对象枚举初始化  类的新实例。
    /// 
    /// 泛型类型 ,用于初始化集合。 为 null。
    public KeyedByTypeCollection(IEnumerable items);
    /// 
    /// 返回集合中第一个具有指定类型的项。
    /// 
    /// 
    /// 
    /// 如果为引用类型,则返回类型  的对象;如果为值类型,则返回类型  的值。 如果集合中不包含类型  的对象,则返回类型的默认值:如果是引用类型,默认值为 null;如果是值类型,默认值为 0。
    /// 
    /// 要在集合中查找的项的类型。
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public T Find();
    /// 
    /// 从集合中移除具有指定类型的对象。
    /// 
    /// 
    /// 
    /// 从集合中移除的对象。
    /// 
    /// 要从集合中移除的项的类型。
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public T Remove();
    /// 
    /// 返回  中包含的类型  的对象的集合。
    /// 
    /// 
    /// 
    /// 一个类型 ,包含来自原始集合的类型  的对象。
    /// 
    /// 要在集合中查找的项的类型。
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public Collection FindAll();
    /// 
    /// 从集合中移除所有具有指定类型的元素。
    /// 
    /// 
    /// 
    /// ,包含来自原始集合的类型  的对象。
    /// 
    /// 要从集合中移除的项的类型。
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public Collection RemoveAll();
    /// 
    /// 获取集合中包含的某个项的类型。
    /// 
    /// 
    /// 
    /// 集合中指定的  的类型。
    /// 
    /// 集合中要检索其类型的项。 为 null。
    [__DynamicallyInvokable]
    protected override Type GetKeyForItem(TItem item);
    /// 
    /// 在集合中的特定位置插入一个元素。
    /// 
    /// 从零开始的索引,应在该位置插入 要在集合中插入的对象。 为 null。
    [__DynamicallyInvokable]
    protected override void InsertItem(int index, TItem item);
    /// 
    /// 使用一个新对象替换指定索引处的项。
    /// 
    /// 要替换的  的从零开始的索引。要添加到集合中的对象。 为 null。
    [__DynamicallyInvokable]
    protected override void SetItem(int index, TItem item);
  }

 2.泛型接口和泛型委托:

     泛型的主要作用就是定义泛型的引用类型和指类型。一个引用类型或值类型可通过指定类型实参的方式实现泛型接口,也可以保持类型实参的未指定状态实现一个泛型接口。

     具体看一下泛型接口IEnumerable:公开枚举数,该枚举数支持在非泛型集合上进行简单迭代。

[ComVisible(true)]
  [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
  [__DynamicallyInvokable]
  public interface IEnumerable
  {
    /// 
    /// 返回一个循环访问集合的枚举数。
    /// 
    /// 
    /// 
    /// 一个可用于循环访问集合的  对象。
    /// 
    /// 2
    [DispId(-4)]
    [__DynamicallyInvokable]
    IEnumerator GetEnumerator();
  }

    CLR支持泛型委托,目的是保证任何类型的对象都能以一种类型安全的方式传给一个回调方法。泛型委托允许一个孩子类型实例在传给一个回调方法时不执行任何装箱处理。委托时机只提供了4个方法:一个构造器,一个Invlke方法,一个BeginInvoke方法和一个EndInvoke方法。如果定义的一个委托类型指定了类型参数,编译器会定义委托类的方法,用指定的类型参数替换方法的参数类型和值类型。

   以上是对泛型类、泛型接口和泛型委托的简单了解,本文的目的主要是讲解泛型方法,下面我们具体了解一些泛型泛型的知识。

三.泛型方法解析:

 1.泛型方法概述:   

    定义泛型类、结构或接口时,类型中定义的任何方法都可引用类型指定的一个类型参数。类型参数可以作为方法的参数,作为方法的返回值,或者作为方法内部定义的一个局部变量来使用。CLR允许一个方法指定它独有的类型参数,这些类型参数可用于参数、返回值、或者局部变量。

   C#编译器支持在调用一个泛型方法时进行类型推断。执行类型推断时,C#使用变量的数据类型,而不是由变量引用的对象的实际类型。一个类型可以定义多个方法,让其中一个方法接受具体的数据类型,让另一个方法接受泛型类型参数。

    泛型方法示例:

List ConverAll(Conver conv)

List:返回类型(一个泛型列表)。

ConverAll:方法名。

:类型参数。

Conver:参数类型(泛型委托)。

conv:参数名。

 对以上的示例代码分析,需要掌握:为每个类型参数使用一个不同的类型,在整体应用这些类型参数。

  (1).首先替换包含方法(List的T部分)的那个类型的类型参数,如将T替换为string:

List ConverAll(Conver conv)

  (2).处理完T后,再需要处理的就是TOutput,可以看出它是一个方法类型参数,这里采用guid替换TOutput。

List ConverAll(Conver conv)

  对TOutput赋予类型实参后,可以移除生命中的类型参数,将方法堪称非泛型方法,如上。以上的示例可以处理一个字符串列表,用一个转换器来生成一个Guid列表。

巧文书
巧文书

巧文书是一款AI写标书、AI写方案的产品。通过自研的先进AI大模型,精准解析招标文件,智能生成投标内容。

下载

  将原始列表中的每个元素都转换成目标类型,将转换后的元素添加到一个列表中,最后返回这个列表。以上的处理方式,主要将其泛型方法的参数进行逐一的细化,无论在什么学科,都需要将复杂的问题进行简单化,将抽象的问题具体化,这也是一种常用的处理方式。

 2.类型约束:

    约束的作用是限制能指定成泛型实参的类型数量。通过限制类型的数量,我们可以对那些类型执行更多的操作。约束可以应用于一个泛型类型的类型参数,也可以应用于一个泛型方法的类型参数。CLR不允许基于类型参数名称或约束进行重载,只能基于元数对类型或方法进行重载。不允许为重写方法的类型参数指定任何约束,但是类型实参的名称是可以改变的。

    泛型约束的操作,约束要放到泛型方法或泛型类型声明的末尾,并由上下文关键where引入。

   (1).引用类型约束:

      引用类型约束:用于确保使用的类型实参是引用类型。(表示为:T:class,且必须为类型参数指定的第一个约束。)

   (2).值类型约束:

      值类型约束:用于确保使用的类型参数是指类型。(表示为:T:struct,可空类型不包含在内)

   (3).构造函数类型约束:

      构造函授类型约束:指定所有类型参数的最后一个约束,它检查类型实参是否有一个可用于创建实例的无参构造函数。(表示为:T:new())适用于所有值类型,所有没有显示声明构造函数的非静态、非抽象类,所有显示声明了一个公共无参构造函数的非抽象类。

   (4).转换类型约束:

     转换类型约束:允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。还可以规定类型实参必须可以转换为另一个类型实参。(例:class Sample where T:Stream)

   (5).组合约束:

     组合约束:所个约束组合在一起的约束,但是组合约束也有限制条件。因为没有任何类型即是引用类型,又是值类型。由于每一个值都有一个无参构造函数,所以假如已经有一个值类型约束,就不允许再指定一个构造函数约束。如果存在多个类型约束,并且其中一个为类,那么它应该出现在接口的前面,而且我们不能多次指定同一个接口。不同的类型参数可以用不同的约束,分别由一个where引入。

   备注:类型推断只适用于泛型方法,不适用于泛型类型。

  以上是对泛型方法的相关概念和约束做了简单的解析,接下来看一下.NET中一些发行方法的具体实现:

/// 
  /// 封装一个方法,该方法具有四个参数并且不返回值。
  /// 
  /// 此委托封装的方法的第一个参数。此委托封装的方法的第二个参数。此委托封装的方法的第三个参数。此委托封装的方法的第四个参数。此委托封装的方法的第一个参数类型。此委托封装的方法的第二个参数类型。此委托封装的方法的第三个参数类型。此委托封装的方法的第四个参数类型。2
  [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
  [__DynamicallyInvokable]
  public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
/// 
  /// 表示比较同一类型的两个对象的方法。
  /// 
  /// 
  /// 
  /// 一个有符号整数,指示  的相对值,如下表所示。 值 含义 小于 0  小于 。 0  等于 。 大于 0  大于 。
  /// 
  /// 要比较的第一个对象。要比较的第二个对象。要比较的对象的类型。1
  [__DynamicallyInvokable]
  public delegate int Comparison(T x, T y);

四.泛型方法应用代码示例:

   以上讲解的有关泛型方法的内容,这里提供一个有关泛型方法操作XML的代码:

/// 
    /// 泛型方法:编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数
    /// 
    public class ObjectXmlSerializer
    {
        /// 
        /// 文件的反序列化
        /// 
        /// 返回值类型
        /// 
        /// 
        /// 如果日志启用,则发生异常时,异常写入日志,若日志没有开启,则直接抛出异常信息
        /// loggingEnabled==true: Null is returned if any error occurs.
        /// loggingEnabled==false: throw exception
        /// 
        public static T LoadFromXml(string fileName) where T : class
        {
            return LoadFromXml(fileName, true);
        }

        /// 
        /// 文件反序列化,若发生异常,异常信息写入日志
        /// 
        /// 加载类的类型
        /// 文件名字
        /// 启用日志记录
        /// 
        public static T LoadFromXml(string fileName, bool loggingEnabled) where T : class
        {
            FileStream fs = null;
            try
            {
                var serializer = new XmlSerializer(typeof(T));
                fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                //反序列化对象
                return (T)serializer.Deserialize(fs);
            }
            catch (Exception e)
            {
                if (loggingEnabled)
                {
                    //文件异常,写入日志
                    LogLoadFileException(fileName, e);
                    return null;
                }
                else
                {

                    throw new Exception(e.Message);
                }
            }
            finally
            {
                if (fs != null) fs.Close();
            }
        }

        /// 
        /// 序列化一个对象到文件中.
        /// 
        /// 
        /// 文件名
        /// 待序列化的数据
        /// 
        /// 如果日志启用,则发生异常时,异常写入日志,若日志没有开启,则直接抛出异常信息
        /// loggingEnabled==true: log exception
        /// loggingEnabled==false: throw exception
        /// 
        public static void SaveToXml(string fileName, T data) where T : class
        {
            SaveToXml(fileName, data, true);
        }

        /// 
        /// 文件反序列化,若发生异常,异常信息写入日志
        /// 
        /// 
        /// 文件名
        /// 发序列化对象
        /// 是否启用日志
        public static void SaveToXml(string fileName, T data, bool loggingEnabled) where T : class
        {
            FileStream fs = null;
            try
            {
                var serializer = new XmlSerializer(typeof(T));
                fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
                //序列化对象
                serializer.Serialize(fs, data);
            }
            catch (Exception e)
            {
                if (loggingEnabled) LogSaveFileException(fileName, e);
                else
                {
                    throw new Exception(e.Message);
                }
            }
            finally
            {
                if (fs != null) fs.Close();
            }
        }

        /// 
        /// 序列化
        /// XML & Datacontract Serialize & Deserialize Helper
        /// 
        /// T指定必须为class类型
        /// 
        /// 
        public static string XmlSerializer(T serialObject) where T : class
        {
            var ser = new XmlSerializer(typeof(T));
            //MemoryStream实现对内存的读写,而不是对持久性存储器进行读写
            //MemoryStream封装以无符号字节数组形式存储的数据,该数组在创建MemoryStream对象时被初始化,
            //或者该数组可创建为空数组。可在内存中直接访问这些封装的数据。
            //内存流可降低应用程序中对临时缓冲区和临时文件的需要。
            var mem = new MemoryStream();
            var writer = new XmlTextWriter(mem, UTF8);
            ser.Serialize(writer, serialObject);
            writer.Close();
            return UTF8.GetString(mem.ToArray());
        }

        /// 
        /// 反序列化
        /// 
        /// 
        /// 
        /// 
        public static T XmlDeserialize(string str) where T : class
        {
            var mySerializer = new XmlSerializer(typeof(T));
            var mem2 = new StreamReader(new MemoryStream(UTF8.GetBytes(str)), UTF8);
            return (T)mySerializer.Deserialize(mem2);
        }

        /// 
        /// 
        /// 
        /// 
        /// 
        /// 返回值类型为传入的类型
        public static T DataContractDeserializer(string xmlData) where T : class
        {
            var stream = new MemoryStream(UTF8.GetBytes(xmlData));
            var reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
            var ser = new DataContractSerializer(typeof(T));
            var deserializedPerson = (T)ser.ReadObject(reader, true);
            reader.Close();
            stream.Close();
            return deserializedPerson;
        }

        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static string DataContractSerializer(T myObject) where T : class
        {
            var stream = new MemoryStream();
            var ser = new DataContractSerializer(typeof(T));
            ser.WriteObject(stream, myObject);
            stream.Close();
            return UTF8.GetString(stream.ToArray());
        }

        /// 
        /// 序列化时异常日志
        /// 
        /// 文件名
        /// 异常
        [Conditional("TRACE")]
        private static void LogLoadFileException(string fileName, Exception ex)
        {
            var sb = new StringBuilder();
            sb.Append("Fail to load xml file: ");
            sb.Append(fileName + Environment.NewLine);
            sb.Append(ex);
            //写入日志记录中方法
            //  Logger.LogEvent(LogCategory, LogEventLoadFileException, sb.ToString());
        }

        /// 
        /// 反序列化时异常日志
        /// 
        /// 文件名
        /// 异常
        [Conditional("TRACE")]
        private static void LogSaveFileException(string fileName, Exception ex)
        {
            var sb = new StringBuilder();
            sb.Append("Fail to save xml file: ");
            sb.Append(fileName + Environment.NewLine);
            sb.Append(ex);

        }


        /// 
        /// 将xml字符串序列化为数据流(数据流编码为ASCII,UTF8)
        /// 
        /// 字符串转换到流
        public static MemoryStream StringXmlToStream(string strXml,Encoding encod)
        {
            MemoryStream memoryStream = null;
            try
            {
                Encoding encoding;
                if (Equals(encod, ASCII))
                {
                     encoding = new ASCIIEncoding();
                }
                else
                {
                     encoding = new UTF8Encoding();  
                }
                var byteArray = encoding.GetBytes(strXml);
                memoryStream = new MemoryStream(byteArray);
                memoryStream.Seek(0, SeekOrigin.Begin);
                return memoryStream;
            }
            catch (IOException ex)
            {
                throw new IOException(ex.Message);
            }
            finally
            {
                if (memoryStream != null) memoryStream.Close();
            }

         

        }
  
    }

   以上的代码就不做赘述,需要次代码的可以使用。

五.总结:

    本文讲解了C#2.0引入的泛型知识,主要包含泛型类、泛型接口、泛型委托,并且重点讲解了泛型方法,已经泛型的约束分类。最后给了一些利用泛型方法操作xml的方法。希望以上的讲解可以帮助到正在想学习的人。

爱知求真,静心钻研,虚心学习,务实创新,细致平和。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

9

2026.01.27

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

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

107

2026.01.26

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

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

13

2026.01.26

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

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

119

2026.01.26

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

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

6

2026.01.26

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

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

6

2026.01.26

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

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

112

2026.01.26

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

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

32

2026.01.26

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

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

100

2026.01.26

热门下载

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

精品课程

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

共94课时 | 7.7万人学习

C 教程
C 教程

共75课时 | 4.2万人学习

C++教程
C++教程

共115课时 | 14.1万人学习

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

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