博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#泛型方法解析
阅读量:6301 次
发布时间:2019-06-22

本文共 14533 字,大约阅读时间需要 48 分钟。

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部分)的那个类型的类型参数,如将T替换为string:

List
 ConverAll
(Conver
 conv)

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

List
 ConverAll(Conver
 conv)

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

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

 2.类型约束:

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

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

   (1).引用类型约束:

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

   (2).值类型约束:

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

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

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

   (4).转换类型约束:

     转换类型约束:允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。还可以规定类型实参必须可以转换为另一个类型实参。(例:class Sample<T> 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();            }                 }      }

复制代码

复制代码

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

本文转自  zddnd  51CTO博客,原文链接:http://blog.51cto.com/13013666/1939715

转载地址:http://uvwxa.baihongyu.com/

你可能感兴趣的文章
搭建FTP-----实现基于mysql验证的虚拟用户
查看>>
不可不知的SUSE Linux二三事
查看>>
Centos下DNS服务器的配置--Master/Slave(二)
查看>>
VOIP
查看>>
Linux系统管理——SELinux使用与管理
查看>>
NPIV介绍(PowerVM 新存储特性)
查看>>
编程的扇入与扇出
查看>>
Android 3.0 r1 API中文文档(113) ——SlidingDrawer
查看>>
Vscode debug eggjs
查看>>
对《关于Dao层职责的思考》一文的修正
查看>>
使用apache-artemis搭建MQTT服务器
查看>>
mysql配置文件详解
查看>>
Mysql主从搭建脚本
查看>>
条形码研究-QR 二维码
查看>>
<基础巩固>二叉树的遍历
查看>>
Java ArrayList、Vector和LinkedList等的差别与用法(转)
查看>>
驱动Led SBC8600
查看>>
mysql零距离接触-数据类型和操作数据表
查看>>
手把手教你看懂并理解Arduino PID控制库——微分冲击
查看>>
linux中删除命令 | rm的介绍
查看>>