第十一章 泛型方法

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

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

原文地址:https://www.cnblogs.com/boywg/p/4149178.html