• 第十一章 泛型方法


    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 小结

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

  • 相关阅读:
    ADPlus
    'telnet' is not recognized as an internal or external command
    图片二进制上传2
    window.opener返回值的用法
    UrlRewriter 重写的问题
    缩略图 水印
    读取二进制图片问题
    正则表达式匹配问题
    .NET读excl数据
    js调用其他页面输出内容
  • 原文地址:https://www.cnblogs.com/boywg/p/4149178.html
Copyright © 2020-2023  润新知