zoukankan      html  css  js  c++  java
  • 第十一章 泛型方法

    1 概述

    1.1 引入泛型方法

      在某些情况下,一个类型中可能只有少数方法成员用到了类型参数,这时就未必需要将整个类型都定义成为泛型。例如在下面的代码中,泛型类GC<T>定义了一个静态方法Contain,用于判断一个元素是否存在于一个数组之中:

    public class GC<T>
    {
        //静态字段
        static readonly double PI=3.14;
        
        //方法
        public static bool Contain(T[] ts,T tp)
        {
            foreach(T t1 in ts)
            {
                if(t1.Equals(tp))
                    return true;
            }
            return false;
        }
    }

      在每次调用该方法时,需要指定该泛型类的一个封闭的构造类型:

    short[] sArray = new short[] {1,3,15,255};
    bool b1 = GC<short>.Contain(sArray,short.MaxValue);//false
    int [] iArray = new int[]{1,3,15,255,65535};
    bool b2=GC<int>.Contain(iArray,ushort.MaxValue);//true

      泛型类GC<T>中定义了一个静态字段PI。由于静态成员属于泛型类的构造类型所有,所以对于每一个构造类型,都为该字段分配了存储空间。

      而下面的程序将类型参数从类的定义中转移到方法的定义中,这就是泛型方法:

        class GenericsMethodSample
        {
            static void Main()
            {
                short[] sArray = new short[] { 1, 3, 15, 255 };
                Console.WriteLine(C.Contain<short>(sArray, short.MaxValue));//false
                int[] iArray = new int[] { 1, 3, 15, 255, 65535 };
                Console.WriteLine(C.Contain<int>(iArray, ushort.MaxValue));//true
            }
        }
        /// <summary>
        /// 泛型方法类
        /// </summary>
        public class C
        {
            //静态字段
            static readonly double PI = 3.14;
    
            //泛型方法
            public static bool Contain<T>(T[] ts, T tp)
            {
                foreach (T t1 in ts)
                {
                    if (t1.Equals(tp))
                        return true;
                }
                return false;
            }
        }

      上面程序中,两次方法的调用都是通过同一个类进行的,静态字段PI在内存中只会占用一个存储。

    1.2 泛型方法的定义

      定义泛型方法也是在方法名之后将类型参数包含在一对分隔符<>中。如果有多个类型参数,则相互间用“,”号分割。之后,所定义的类型参数既可以作为方法的参数类型和返回类型,也可以用来在方法的执行代码中定义局部变量。除此之外,泛型方法的定义规则与普通方法的定义规则相同。

      如果在同一个类型中定义了多个泛型方法,它们的类型参数是互不相关的。

      如果泛型方法属于一个泛型类型,而二者又定义了相同的类型参数,那么类型参数的使用也服从“屏蔽规则”,即泛型方法中出现的类型参数属于该方法的定义,而在类型的其它成员中出现的类型参数属于类的定义。例如:

    public class GArith<T>
    {
        private T[] m_list;
    
        public static void Swap<T>(ref T tp1,ref T tp2)
        {
            ...
        }
    }

      这时为泛型类GArith<T>创建任何一个构造类型的实例,其私有有字段m_list类型都被替换为相应的封闭类型,但不会影响到泛型方法Swap<T>的参数类型。同样,指定给方法的封闭类型也和类的构造类型无关。例如:

    int x=2;
    int y=5;
    GArith<string>.Swap<int>(ref x,ref y);//correct
    Garith<int>.Swap<string>(ref x,ref y);//error:传递类型与实际类型不同

      所以,为了提高程序的可读性,应尽量避免为泛型类型及其泛型的成员方法定义同名的类型参数。

      在泛型方法中同样可以对类型参数进行限制,限制方式和泛型类的相同

    public static T Max<T>(T tp1,T tp2) where T : IComparable<T>
    {
        if(tp1.CompareTo(tp2) > 0)
            return tp1;
        else
            return tp2;
    }

      泛型方法既可以属于普通类型,也可以属于泛型类型(泛型类、泛型结构、泛型接口)。

      C#中不允许定义泛型的属性、事件、索引函数和操作符。???

    1.3 调用泛型方法

      调用泛型方法有两种方式:

      一是在调用时显式指定方法的类型参数列表,前面的例子就是这这种方式。

    Console.WriteLine(C.Contain<short>(sArray,short.MaxValue));

      二是在调用时省略方法的类型参数列表:

    Console.WrtiteLine(C.Contain(sArray,short.MaxValue));

      由于传递给泛型方法的两个参数类型分别是short[]和short,调用的过程和结果与完整调用是一样的。

      在采用简写调用方式时,编译器需要检查每个参数的类型,并试图由此推断出每个类型参数最终被哪个封闭类型所取代。这个过程称为类型推断。如果推断成功,则会将简写调用代码映射到对应的完整调用代码;否则编译就会失败。

      编译器进行类型推断的算法较为复杂,简单的理解是:在泛型方法的声明中出现相同的类型参数的地方,调用时必须以相同的封闭类型去替换这些类型参数,否则类型推断就会失败,例如下面的代码就是错误的:

    Console.WriteLine(C.Contain(iArray,ushort.MaxValue));

      因为方法的第一个参数,T被替换成int;而对于第二个参数则被替换成ushort,所以转换失败。如果仍要采用简写方式,则应首先进行类型转换:

    int x = ushort.MaxValue;
    Console.WriteLine(C.Contain(iArray,x));

      或者合并为下面的形式(尽管ushort类型可以隐式转换到int类型,但在简写调用泛型方法时仍然需要对参数采用显式转换的格式。这一点和调用普通方法以及完整调用泛型方法是不同的):

    Console.WriteLine(C.Contain(iArray,(int)ushort.MaxValue));

    1.4 惟一性规则

      如果泛型方法的名称相同,那么通过指定不同的类型参数能否使方法具有不同的标识呢?可以分几种情况来讨论:

      (1)有无类型参数以及以及类型参数的不同个数,足以区分不同的方法

    public class C
    {
      public
    void F(){}   public void F<T>(){}   public void F<T,S>(){}
    }

      (2)仅仅是类型参数的名称不同,不足以区分不同的方法:

    public class C
    {
      public
    void F<T>(T t1){}   public void S<S>(S s1){}//error
    }

      (3)类型参数作为传递给方法的参数类型时,不同的参数类型足以区分不同的方法,但在调用时,因为类型参数的替换出现了多个可能,在调用时就可能是错误。

    public class C
    {
      public
    static void F<R,S>(R r1,S s1){}   public static void F<R,S>(S s1,R r1){}
    }

      对于以上定义,下面的第一次调用是合法的,而第二次调用会出现错误

    int x=2;
    int y=3;
    string s="hello";
    C.F<string,int>(s,x);
    C.F<int,int>(x,y);//error

      (4)如果泛型方法属于泛型类型所有,而外部类型的类型参数在方法中出现。这种情况较为复杂,但判断规则和第3种情况相同,即不同的参数类型足以区分不同的方法,而在调用时不允许出现歧义。例如下面的定义都是合法的:

    public class GA<T>
    {
    }
    
    public class GC<T>
    {
        public static void F<S>(T t1,S s1){}
        public static void F<S>(S s1,T t1){}
        public static void F<R,S>(GA<R> a,GA<S> b){}
        public static void F<R,S>(GA<S> a,GA<R> b){}
    }

      但在下面的调用代码中,最后两行是错误的,它们会引起歧义:

    int x=2;
    int y=3;
    string s="hello";
    GC<string>.F<int>(s,x);
    GC<string>.F<int,double>(new GA<int>(),new GA<double>());
    GC<int>.F<int>(x,y);//error:与方法一和方法二存在歧义
    GC<string>.F<int,int>(new GA<int>(),new GA<int>());//error:与方法三和方法四存在歧义

      (5)泛型方法的类型限制不是方法标识的一部分,不同的限制不足以区分不同的方法。例如下面的方法定义都是重复的:

    public class C
    {
        public static void F<T>() where T : IComparable {}
        public static void F<T>() where T : IComparable<T> {}
        public static void F<T>() where T : IComparer<T> {}
        public static void F<U>() {}
    }

    2 泛型方法的重载

    2.1 概述

      和普通方法一样,泛型方法也可以被定义成为虚拟方法、重载方法、抽象方法或密封方法。普通方法的继承和多态性的内容同样适用于泛型方法。当泛型方法属于某个泛型类型时,8章中2.3小节中讲的嵌套泛型类的规则和8章中5小节泛型类之间的继承的规则也同样适用。

      下面的代码示范了泛型方法的继承及重载

    public abstract class GA
    {
        public abstract void MethodA<T>(T tp1,T tp2);
    }
    
    public class GB<T> : GA
    {
        public override void MethodA<R>(R r1,R r2) {}
        public virtual void MethoB<S>(S sp,T tp) {}
    }
    
    public class GC<T> : GB<int>
    {
        public override void MethodA<R r1,R r2) {}
        public sealed override void MethodB<S>(S sp,int i2) {}
    }

      和泛型类之间的继承类似,派生类在继承或重载基类中的泛型方法时,同时也继承了基类中对泛型方法的类型限制。不过在泛型方法继承中,不需要在派生类的泛型方法中再明确写出这种限制。但如果在派生类中使用new修饰符对基类中的泛型方法进行了覆盖,那么基类中对泛型方法的类型限制则不再有效。

      例如对于下面的类和继承定义,派生类Derived重载了基类的泛型方法Method1<S>,虽然它没有显式地写出对类型参数S的限制,但在方法调用时仍然要满足限制要求:

    public class A
    {
    }
    
    public abstract class Base
    {
        public abstract void Method1<S>(S[] ss) where S : IComparable;
        public virtual void Method2<T>(T tp1,T tp2) where T : IComparable<T>{}
    }
    
    public class Derived : Base
    {
        public override void Method1<S>(S[] ss) {}
        public new void Method2<T>(T tp1,T tp2){}
    }

      下面的代码中,调用Derived对象的Method1<int[]>方法是合法的,而调用其Method1<A[]>则是不合法的,因为类A的定义中并没有说明它继承的接口IComparable:

    Derived d1 = new Derived();
    int[] iArray = new int[5];
    d1.Method1<int>(iArray);
    A[] array = new A[5];
    d1.Method1<A>(array);//error

      而对于方法Method2<T>,它在隐藏基类方法的同时也隐藏了对类型限制的要求。下面的代码是完全合法的:

    Derived d1 = new Derived();
    A a1 = new A();
    A a2 = new A();
    d1.Method2(a1,a2);

    2.2 示例程序:读写器

      本节将使用泛型方法来进一步改进管理联系人类型的应用程序。将对联系人内容的输入输出工作抽象出来,放在一个新定义的读写器类中加以管理。而通过读写器类的不同派生类,可以实现不同方式的输入输出,如控制台输入输出、Windows窗体输入输出、文件流输入输出。

    2.2.1 读写器定义

        /// <summary>
        /// 抽象类:读写器
        /// </summary>
        public abstract class RW
        {
            public abstract bool OpenRead();
            public abstract bool OpenWrite();
            public abstract void CloseRead();
            public abstract void CloseWrite();
            public abstract string Read(string sPrompt);
            public abstract void Write(string sPrompt, string sContent);
        }
        /// <summary>
        /// 派生类:控制台读写器
        /// </summary>
        public class ConsoleRW:RW
        {
            public override bool OpenRead()
            {
                return true;
            }
    
            public override bool OpenWrite()
            {
                return true;
            }
    
            public override void CloseRead() { }
    
            public override void CloseWrite() { }
    
            public override string Read(string sPrompt)
            {
                Console.WriteLine("请输入{0}", sPrompt);
                return Console.ReadLine();
            }
    
            public override void Write(string sPrompt, string sContent)
            {
                Console.WriteLine("{0}:{1} ", sPrompt, sContent);
            }
        }
        /// <summary>
        /// 派生类:不带分行的控制台读写器
        /// </summary>
        public class NoBreakConsoleRW:ConsoleRW
        {
            public override void Write(string sPrompt, string sContent)
            {
                Console.Write("{0}:{1} ", sPrompt, sContent);
            }
        }
    View Code

    2.2.2 联系人定义

        class GenericsMethodOverrideSample
        {
            static void Main()
            {
                Contact[] cons = new Contact[100];
                ConsoleRW rw = new ConsoleRW();
                int iCount = 0;
                while (rw.Read("继续输入联系人信息?(Y/N)").ToUpper()!="N")
                {
                    cons[iCount]=new Contact();
                    cons[iCount].Input(rw);
                    iCount++;
                    if(iCount==100)
                        break;
                }
                if(iCount==0)
                    return;
                if (rw.Read("请选择分块输出(按任意键)或单行输出(S)").ToUpper() == "S")
                    rw = new NoBreakConsoleRW();
                for (int i = 0; i < iCount; i++)
                {
                    cons[i].Output(rw);
                    Console.WriteLine();
                }
            }
        }
        /// <summary>
        /// 基类:联系人Contact
        /// </summary>
        public class Contact:IComparable<Contact>
        {
            //字段
            protected string m_name = "未知";
            protected string m_gender = "";
            protected string[] m_phones;
    
            //属性
            public string Name
            {
                get
                {
                    return m_name;
                }
                set
                {
                    m_name = value;
                }
            }
    
            public string Gender
            {
                get
                {
                    return m_gender;
                }
                set
                {
                    if (value == "" || value == "")
                        m_gender = value;
                }
            }
    
            //构造函数
            public Contact()
            {
                m_phones = new string[3];
            }
    
            public Contact(string sName)
            {
                m_name = sName;
                m_phones = new string[3];
            }
    
            //方法
            public int CompareTo(Contact con)
            {
                return this.m_name.CompareTo(con.m_name);
            }
    
            public bool Equals(Contact con)
            {
                return this.Name.Equals(con.Name);
            }
    
            public virtual void Input<T>(T tp) where T : RW
            {
                m_name = tp.Read("姓名");
                Gender = tp.Read("性别");
                m_phones[0] = tp.Read("住宅电话");
                m_phones[1] = tp.Read("办公电话");
                m_phones[2] = tp.Read("手机");
            }
    
            public virtual void Output<T>(T tp) where T : RW
            {
                tp.Write("姓名", m_name);
                tp.Write("性别", m_gender);
                tp.Write("住宅电话", m_phones[0]);
                tp.Write("办公电话", m_phones[1]);
                tp.Write("手机", m_phones[2]);
            }
        }
        /// <summary>
        /// 派生类:商务Business
        /// </summary>
        public class Business:Contact
        {
            //字段
            protected string m_company = "";
            protected string m_title = "";
    
            //属性
            public string Company
            {
                get
                {
                    return m_company;
                }
                set
                {
                    m_company = value;
                }
            }
    
            public string Title
            {
                get
                {
                    return m_title;
                }
                set
                {
                    m_title = value;
                }
            }
    
    
            //构造函数
            public Business()
            {
                m_phones = new string[4];
            }
    
            public Business(string sName)
            {
                m_name = sName;
                m_phones = new string[4];
            }
    
            //重载方法
            public override void Input<T>(T tp)
            {
                m_name = tp.Read("姓名");
                Gender = tp.Read("性别");
                m_company = tp.Read("公司");
                m_title = tp.Read("职务");
                m_phones[0] = tp.Read("办公电话");
                m_phones[1] = tp.Read("商务电话");
                m_phones[2] = tp.Read("住宅电话");
                m_phones[3] = tp.Read("手机");
            }
    
            public override void Output<T>(T tp)
            {
                tp.Write("姓名", m_name);
                tp.Write("性别", m_gender);
                tp.Write("公司", m_company);
                tp.Write("职务", m_title);
                tp.Write("办公电话", m_phones[0]);
                tp.Write("商务电话", m_phones[1]);
                tp.Write("住宅电话", m_phones[2]);
                tp.Write("手机", m_phones[3]);
            }
    
        }
        /// <summary>
        /// 派生类:同学Classmate
        /// </summary>
        public class Classmate:Contact
        {
            //字段
            protected DateTime m_birthday;
    
            //属性
            public DateTime Birthday
            {
                get
                {
                    return m_birthday;
                }
                set
                {
                    m_birthday = value;
                }
            }
    
            //构造函数
            public Classmate()
                : base()
            {
            }
    
            public Classmate(string sName)
                : base(sName)
            {
            }
    
            //方法
            public override void Input<T>(T tp)
            {
                base.Input(tp);
                m_birthday = DateTime.Parse(tp.Read("生日(yyyy-mm-dd):"));
            }
    
            public override void Output<T>(T tp)
            {
                base.Output(tp);
                tp.Write("生日:", m_birthday.ToShortDateString());
            }
        }
    View Code

      程序通过Contact类中定义的泛型方法进行内容的输入和输出。运行结果如下:

    请输入继续输入联系人信息?(Y/N)
    Y
    请输入姓名
    张三
    请输入性别
    男
    请输入住宅电话
    666666
    请输入办公电话
    888888
    请输入手机
    13888888888
    请输入继续输入联系人信息?(Y/N)
    N
    请输入请选择分块输出(按任意键)或单行输出(S)
    
    姓名:张三
    性别:男
    住宅电话:666666
    办公电话:888888
    手机:13888888888
    
    请按任意键继续. . .
    

      上面的程序将所有的联系人都视为同一种类型。而如果要实现不同类别的联系人的管理功能,对程序进行改进,主要是增加联系人类别的选择功能。  

        class GenericsReadWriteSample
        {
            static void Main()
            {
                Contact[] cons = new Contact[100];
                ConsoleRW rw = new ConsoleRW();
                int iCount = 0;
                while (rw.Read("继续输入联系人信息?(Y/N)").ToUpper() != "N")
                {
                    string sType = rw.Read("请选择类别(1.普通 2.商务 3.同学)");
                    if (sType == "1")
                        cons[iCount] = new Contact();
                    else if (sType == "2")
                        cons[iCount] = new Business();
                    else if (sType == "3")
                        cons[iCount] = new Classmate();
                    else
                        break;
                    cons[iCount].Input(rw);
                    iCount++;
                    if (iCount == 100)
                        break;
                }
                if (iCount == 0)
                    return;
                if (rw.Read("请选择分块输出(按任意键)或单行输出(S)").ToUpper() == "S")
                    rw = new NoBreakConsoleRW();
                for (int i = 0; i < iCount; i++)
                {
                    cons[i].Output(rw);
                    Console.WriteLine();
                }
            }
        }
    View Code

      程序运行结果:

    请输入继续输入联系人信息?(Y/N)
    y
    请输入请选择类别(1.普通 2.商务 3.同学)
    1
    请输入姓名
    张三
    请输入性别
    男
    请输入住宅电话
    666666
    请输入办公电话
    888888
    请输入手机
    138888888
    请输入继续输入联系人信息?(Y/N)
    Y
    请输入请选择类别(1.普通 2.商务 3.同学)
    2
    请输入姓名
    李四
    请输入性别
    男
    请输入公司
    Microsoft
    请输入职务
    经理
    请输入办公电话
    666666
    请输入商务电话
    888888
    请输入住宅电话
    999999
    请输入手机
    13999999999
    请输入继续输入联系人信息?(Y/N)
    Y
    请输入请选择类别(1.普通 2.商务 3.同学)
    3
    请输入姓名
    王五
    请输入性别
    女
    请输入住宅电话
    666666
    请输入办公电话
    888888
    请输入手机
    13777777777
    请输入生日(yyyy-mm-dd):
    1981-12-01
    请输入继续输入联系人信息?(Y/N)
    N
    请输入请选择分块输出(按任意键)或单行输出(S)
    S
    姓名:张三 性别:男 住宅电话:666666 办公电话:888888 手机:138888888
    姓名:李四 性别:男 公司:Microsoft 职务:经理 办公电话:666666 商务电话:888888
     住宅电话:999999 手机:13999999999
    姓名:王五 性别:女 住宅电话:666666 办公电话:888888 手机:13777777777 生日::1
    981-12-1
    请按任意键继续. . .
    View Code

     4 小结

      泛型的概念不只适用于类型,也适用于类型的方法成员。泛型方法通过类型参数对普通方法进行了抽象,只需要一次定义就可以扩展到多种类型。和普通方法一样,泛型方法需要满足惟一性规则,也可以在类的继承层次中进行重载,还可以通过代表来封装和调用。使用泛型方法的关键是把握类型参数及其封闭类型之间的关系。

  • 相关阅读:
    Brain network involved in autonomic functions 与自主功能相关的大脑网络
    Brief summary of classical components of ERP 事件相关成分(ERP)经典成分小结
    ICA & Percentage Variance Account For (PVAF)
    数据处理中白化Whitening的作用图解分析
    Loadings vs eigenvectors in PCA 主成分分析(PCA)中的负荷和特征向量
    主成分分析(PCA)和独立成分分析(ICA)相关资料
    Sketch of heart and QRS complex 心脏及QRS波群简图
    Brain Network visulation in EEG 脑电网络可视化
    Phase Locking Value (PLV) 神经信号的锁相值
    ubuntu16.04下的一些基本操作笔记
  • 原文地址:https://www.cnblogs.com/boywg/p/4149178.html
Copyright © 2011-2022 走看看