C#学习笔记(十二) 泛型

本文围绕C#泛型展开,介绍泛型允许多种类型共享代码,有类、接口等五种类型。阐述了利用泛型实现栈操作,类型参数的约束,泛型方法的声明与调用。还提及泛型类与扩展方法结合,以及泛型结构、委托、接口的使用,最后讲解了协变与逆变。

第十七章 泛型

  1. 泛型:允许多种类型共享一组代码。在声明泛型时,给出类型参数,然后用不同类型创建实例。也就是在事先使用类型占位符,在实例化时才指出真实类型。
  2. 泛型五种类型:类、接口、结构、委托和方法。泛型是类型的模板,而类型是对象的模板。
  3. 利用泛型实现栈的操作:
//泛型的基本用法
using System;

namespace test28
{
    class Program
    {
        //利用泛型实现栈的操作
        class Myclass<T>      //声明泛型,T,S为类型占位符
        {
            T[] StackArray;
            int StackPointer = 0;
            public void Push(T x)
            {
                if(!IsStackFull)
                StackArray[StackPointer++] = x;
            }
            public T Pop()
            {
                return (!IsStackEmpty)?StackArray[--StackPointer]:StackArray[0];             
            }
            const int MaxStack = 10;
            bool IsStackFull {get{return StackPointer >= MaxStack;}}
            bool IsStackEmpty {get{return StackPointer <= 0;}}
            public Myclass()
            {
                StackArray = new T[MaxStack];
            }
            public void Print()
            {
                for(int i = StackPointer -1;i>=0;i--)
                {
                    Console.WriteLine(" Info:{0}",StackArray[i]);
                }
            }
        }
        static void Main(string[] args)
        {
            Myclass<int> StackInt = new Myclass<int>();
            Myclass<string> StackString = new Myclass<string>();
            StackInt.Push(6);
            StackInt.Push(8);
            StackInt.Push(10);
            StackInt.Print();
            StackString.Push("加油!2019!");
            StackString.Push("自动化1401班!");
            StackString.Print();
        }
    }
}

执行结果:

在这里插入图片描述

  1. 类型参数的约束:使用where子句对类型参数进行约束,where子句之间没有标点符号,如果某个类型参数有多个约束,在每一个where子句中用逗号间隔。where TypeParams:constraint,constraint,... 示例如下
class Myclass<T1,T2>
where T1:Custom
where T2:IComparable
{
	...
}
  1. 约束类型和次序
    类名:只有这个类型的类或者从它继承的类才能用作类型实参
    class:任何引用类型,包括类、数组、委托和接口都可以用作类型实参。
    struct:任何值类型都可以用作类型实参。
    接口名:只有这个接口或者实现这个接口的类型才可以用作类型实参。
    new():任何带有无参公共构造函数的类型都可以用作类型实参,即为构造函数约束。
  2. where子句的约束必须有一定的顺序,最多只能有一个主约束;可以有任意多的接口名约束;如果存在构造函数约束,则必须放在最后。
class SortedList<S>
			where S:IComparable<S>{....}
class LinkedList<M,N>
			where M:IComparable<M>
			where N:IComparable{....}
class MyDictionary<KeyType,ValueType>
			where Keytype:IEnumerable,
			new()                {...}			
  1. 泛型方法:它有两个参数列表,位于圆括号里的方法参数列表,位于尖括号里的类型参数列表。要声明泛型方法,要在方法名之后一次是类型参数列表和方法列表,在方法参数列表后是可选的约束子句
public void PrintData<S,T>(S p,T t) 
				where S:Person
{
	...
}				
  1. 调用泛型方法: 在方法调用时提供类型实参,Mymethod<short,int>();Mymethod<int,long>();当然,这里也可用推断类型,倘若方法参数列表的类型已知,编辑器可以推断出类型参数列表的情况,这是就可以将类型参数列表给忽略,int myint = 5;Mymethod<int>(myint);可简写为Mymethod(myint);
  2. 泛型方法示例
class Simple		//非泛型类
{
	static public void ReverseAndPrint<T>(T[] arr)		//泛型方法
	{
		Array.Reverse(arr);
		foreach(T item in arr)
		{
			Console.WriteLine("{0},",item.ToString());
		}
		Console.WriteLine(" ");
	}
}
class Program
{
	static void Main()
	{
		//创建各种类型的数组
		var intArray    = new int[]{3,5,7,9};
		var stringArray = new string[]{"first","second","third"};
		var doubleArray = new double[]{3.456,4.567,5.678};
		Simple.ReverseAndPrint<int>(intArray);		         	//调用方法
		Simple.ReverseAndPrint(intArray);		                //推断引用
		Simple.ReverseAndPrint<string>(stringArray);			//调用方法
		Simple.ReverseAndPrint(intArray);		                //推断引用
		Simple.ReverseAndPrint<double>(doubleArray);			//调用方法
		Simple.ReverseAndPrint(doubleArray);		            //推断引用
	}
}

执行结果:

在这里插入图片描述

  1. 扩展方法和泛型类:将泛型类与扩展方法结合,允许我们将类中的静态方法关联到不同的泛型上,还可以像调用类结构实例那样调用方法。泛型类的扩展方法必须声明为static;必须是静态类的成员;第一个参数类型必须为this,后面是扩展的泛型类的名称,示例如下:
//扩展方法和泛型类
using System;

namespace test30
{
    static class ExtendHoleder
    {
        public static void Print<T>(this Holder<T> h)
        {
            T[] vals = h.GetValues();
            Console.WriteLine("{0},\t{1},\t{2}",vals[0],vals[1],vals[2]);
        }
    }
    class Holder<T>
    {
        T[] vals = new T[3];
        public Holder(T t0,T t1,T t2)
        {vals[0]=t0;vals[1]=t1;vals[2]=t2;}
        public T[] GetValues(){return vals;}
    }
    class Program
    {
        static void Main(string[] args)
        {
            var intHolder = new Holder<int>(3,5,7);
            var stringHolder = new Holder<string>("a1","b2","c3");
            intHolder.Print();
            stringHolder.Print();
        }
    }
}


执行结果是:

在这里插入图片描述

  1. 泛型结构:与泛型类相似,泛型结构可以有类型参数和约束。泛型结构的规则和条件与泛型类是一样的。示例如下
//泛型结构
using System;

namespace test31
{
    struct PieceOfData<T>
    {
        public PieceOfData(T value){_data = value;}
        private T _data;
        public T Data
        {
            get{return _data;}
            set{_data = value;}
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var intData = new PieceOfData<int>(16);
            var stringData = new PieceOfData<string>("hi nihao!");
            Console.WriteLine("intData    ={0}",intData.Data);
            Console.WriteLine("stringData ={0}",stringData.Data);
        }
    }
}

执行结果:

在这里插入图片描述

  1. 泛型委托:泛型委托与非泛型委托相似,只是类型参数决定了能接受什么样的方法。要声明泛型委托,在委托名之后要有类型参数列表和委托参数列表;类型参数包括返回值、形参列表和约束子句。示例如下,泛型委托与泛型类。
//泛型委托的简单示例
using System;

namespace test32
{
    delegate T PrintT<T>(T t);     //声明泛型委托,返回类型和参数列表类型都是T
    public class Myclass<T>	       //声明泛型类
    {
        public T Method(T t)       //方法的参数和返回类型与委托匹配
        {
            return t;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Myclass<string> mycl = new Myclass<string>();
            PrintT<string> prt = new PrintT<string>(mycl.Method); //等价于PrintT<string> prt = mycl.Method;  创建委托对象并赋值给委托变量
            Console.WriteLine(prt("越努力越幸运,加油,自动化1401班!")); //执行委托
        }
    }
}

执行结果是:

在这里插入图片描述

  1. 泛型委托示例二,注意,此示例是泛型委托和非泛型类的使用。
//泛型委托示例二
using System;

namespace test33
{
    delegate T3 SumNum<T1,T2,T3>(T1 t1,T2 t2);      //声明泛型委托,返回类型为T3,类型参数为T1,T2,T3,委托参数为T1,T2
    public class Myclass                            //声明非泛型类
    {
        private int _data;
        public string Method(int t1,int t2)         //泛型方法的参数与返回类型与委托匹配
        {
            _data = t1 + t2;
            return _data.ToString();
        }

    }
    class Program
    {
        static void Main(string[] args)
        {
            Myclass mycl = new Myclass();
            SumNum<int,int,string> sumn = new SumNum<int,int,string>(mycl.Method);       //创建委托对象并赋值给委托变量
            Console.WriteLine(sumn(4,5));       //执行委托
        }
    }
}

执行结果是:9

  1. 泛型接口:泛型接口的类型参数决定了接口成员的参数类型和返回类型。在接口名后跟类型参数;泛型接口既可以在泛型类中实现也可以在非泛型类中实现;实现不同类型参数的泛型接口是不同的接口;泛型接口的实现必须唯一,不能重复实现相同的接口。
  2. 泛型接口示例:
//泛型接口示例一
using System;

namespace test34
{
    class Program
    {
        public interface Multify<T>               //声明泛型接口
        {T Mulfy(T t);}
        public class Myclass<T>: Multify<T>       //在泛型类中实现泛型接口
        {
            public T Mulfy(T t)
            {
                return t;
            }        
        }
        static void Main(string[] args)
        {
            Myclass<int> mycl = new Myclass<int>();
            Console.WriteLine(mycl.Mulfy(16));
        }
    }
}

代码过于简单,只是为了演示,结果是16

  1. 协变与逆变:协变允许将派生类的委托对象赋值给基类变量,逆变则与之相反;当然协变和逆变都可用于接口;协变的(委托或接口)类型参数必须声明为输出参数(关键字out),逆变的类型参数必须声明为输入参数(关键字in),在实现委托方法时,如果是协变,则方法的参数传入派生类的引用,实现派生类的方法;如果是逆变,传入基类的引用,实现基类的成员;协变的委托没有委托参数,逆变的委托有委托参数。
  2. 委托的协变和逆变示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace test35
{
    class Myclass        //声明基类
    {
      public string[] color = {"red","blue","yellow","orange","green"};
    }
    class Myderived : Myclass    {} //声明派生类
    class Program
    {
        delegate void Mydele<out T>();     //声明泛型委托,协变时不能有委托参数,类型参数有out修饰
        delegate void Mydele1<in T>(T t);     //声明泛型委托,逆变时有委托参数,类型参数有in修饰
        public static void Method()     //协变,方法要与委托Mydele相匹配,在方法中实现派生类
        {
            Myderived mydr = new Myderived();
            Array.Reverse(mydr.color);      //将数组倒序排列
            foreach(string item in mydr.color)
            {
                Console.WriteLine(item);
            }
        }
        public static void Method1(Myclass mycl)        //逆变,要与委托Mydele1相匹配,传入基类的引用,实现基类的成员
        {
             foreach(string item in mycl.color)
            {
                Console.WriteLine(item);
            }
        }
        static void Main(string[] args)
        {
            Console.WriteLine("................协变................");
            Mydele<Myderived> mydel = Method;       //创建委托对象
            Mydele<Myclass> mycl = mydel;      //协变时,将派生类的委托对象赋值给基类的委托变量是可行的
            mycl();     //执行委托
            Console.WriteLine("................逆变................");
            Mydele1<Myclass> mycll = Method1;       //创建委托对象
            Mydele1<Myderived> mydell = mycll;      //逆变时,将基类的委托对象赋值给派生类的委托变量是可行的
            mydell(new Myderived());        //执行委托,传入派生类的对象
        }
    }
}

执行结果:

在这里插入图片描述
19. 接口的协变示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace test36
{
    //接口的协变
    class Animal{public string Name;}       //声明基类
    class Dog:Animal{};                     //声明派生类
    interface IMyIfc<out T>  //声明泛型接口
    {
        T GetFirst();
    }
    class SimpleReturn<T>:IMyIfc<T>   //声明泛型类,实现泛型接口
    {
        public T[] items = new T[2];
        public T GetFirst(){return items[0];}
    }
    class Program
    {
        static void DoSomething(IMyIfc<Animal> returner)  //传入接口的引用
        {
            Console.WriteLine(returner.GetFirst().Name);
        }
        static void Main(string[] args)
        {
            SimpleReturn<Dog> dogReturner = new  SimpleReturn<Dog>();
            dogReturner.items[0] = new Dog(){Name = "WorkHarding"};
            IMyIfc<Animal> animalReturner = dogReturner;
            DoSomething(dogReturner);
        }
    }
}

执行结果是:

在这里插入图片描述

  1. 接口的逆变示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace test37
{
    //接口的逆变
    class Mybase
    {
        public string[] str = {"red","orange","yellow","green","blue","violet"};
    }
    class Myderived : Mybase{}
    interface  IMyIfc<in T>     //定义泛型接口,并且接口没有返回类型
    {
       string GetFirst();
    }
    class MyColor<T>:IMyIfc<T>      //在泛型类中实现接口
    {
        public string[] Color = new string[6] ;
        public string GetFirst()
        {
            Myderived mydr = new Myderived();
            for(int i = 0;i<6;i++)
            {
                Color[i] = mydr.str[i];
            }
            return Color[0];
        }
    }
    class Program
    {
        public static void Choice(IMyIfc<Myderived> t)		//传入接口的引用
        {
            Console.WriteLine(t.GetFirst());
        }
        static void Main(string[] args)
        {
            MyColor<Mybase> myco = new MyColor<Mybase>();
            IMyIfc<Myderived> t = myco;
            Choice(myco);
        }
    }
}

执行结果是:red

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值