一种类型占位符,或称之为类型参数。我们知道一个方法中,一个变量的值可以作为参数,但其实这个变量的类型本身也可以作为参数。泛型允许我们在调用的时候再指定这个类型参数是什么。在.net中,泛型能够给我们带来的两个明显好处是——类型安全和减少装箱、拆箱。
例如:通常一个方法或过程的签名都是有明确的数据类型的。如 :
public void ProcessData(int i){}
public void ProcessData(string i){}
public void ProcessData(decimal i){}
public void ProcessData(double i){}
等。
这些方法的签名中的:int ,string,decimal,double 都是明确的数据类型,程序员访问这些方法的过程中需要提供提定类型的参数:
ProcessData(123);
ProcessData("abc");
ProcessData("12.12")
而如果我们将int ,string,decimal,double这些类型也当成一种参数传给方法的时候方法的定义便是这样:
public void ProcessData<T>(T i){} //T是int ,string,decimal,double这些数据类型的指代
用户在调用的时候便成了这样:
ProcessData<string>("abc");
ProcessData<int>(123);
ProcessData<double>(12.23);
这与通常的那些定义的最大区别是,方法的定义实现过程只有一个。但是它具有处理不同的数据类型数据的能力。
C# 2.0中有如List<>等泛型对象都具有此特性。
具有泛型机制的软件开发平台及语言
.Net 平台 2.0及以上版本
JAVA 5及以上版本
泛型的好处:
泛型是c#2.0的一个新增加的特性,它为使用c#语言编写面向对象程序增加了极大的效力和灵活性。它允许程序员将一个实际的数据类型的规约延迟至泛型的实例被创建时才确定。泛型为开发者提供了一种高性能的编程方式,能够提高代码的重用性,并允许开发者编写非常优雅的解决方案。
数据层:
public List<libs.Model.Artitle> GetAllArt()
{
List<libs.Model.Artitle> list = new List<Artitle>();
string sqlconn = System.Configuration.ConfigurationSettings.AppSettings["sqlconn"];
SqlConnection conn = new SqlConnection(sqlconn);
string sqlstr = "select titleid,Title,author,company,Uploaddate,isVidate,conimages,content from writings order by titleid asc";
SqlCommand cmd = new SqlCommand (sqlstr,conn);
try
{
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
libs.Model.Artitle artles = new Artitle();
artles.Titleid = int.Parse(reader["titleid"].ToString());
artles.Title = reader["title"].ToString();
artles.Uploaddate = DateTime.Parse(reader["Uploaddate"].ToString());
list.Add(artles);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
conn.Close();
}
return list;
}
逻辑层:
public List<Artitle> GettitleAll()
{
return new libs.DAL.ArtileAccess().GetAllArt();
}
web层调用:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
this.GetallArt();
}
}
public void GetallArt()
{
libs.Log.Artitless atrlesst = new libs.Log.Artitless();
this.Repeater1.DataSource = atrlesst.GettitleAll(); //或者直接调用数据库层:
//this.Repeater1.DataSource = new libs.DAL.ArtileAccess().GetAllArt();
this.Repeater1.DataBind();
}
泛型较为广泛地被讨论,这里写到的只是作为新手的入门级认识。
泛型最常应用于集合类。
泛型的一个显而易见的优点在于可以在许多操作中避免强制转换或装箱操作的成本或风险,拿ArrayList这个集合类来说,为了达到其通用性,集合元素都将向上转换为object类型,对于值类型,更是有装箱拆箱的成本:
static void Main(string[] args)
{
ArrayList al = new ArrayList();
al.Add(1);
}
在IL中是:
IL_0008: ldc.i4.1
IL_0009: box [mscorlib]System.Int32
IL_000e: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
box操作就是装箱,具体过程是把值类型从栈中弹出,放入堆中,同时把在堆中的地址压入到栈中,频繁出现这样的操作,成本比较大。
所以在2.0中,遇到以上的应用,应该使用泛型集合类List<T>:
static void Main(string[] args)
{
List<int> l = new List<int>();
l.Add(1);
}
另一个比较常用的泛型集合类是Dictionary<T,T>,用于保存键值对:
static void Main(string[] args)
{
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1, "SomeBook1");
dict.Add(2, "SomeBook2");
dict.Add(3, "SomeBook3");
Console.WriteLine(dict[2]);//output:SomeBook2
dict[2] = "SomeCD1";//modify
Console.WriteLine(dict[2]);//output:SomeCD1
dict.Remove(2);//delete
foreach (KeyValuePair<int, string> kv in dict)
{
Console.WriteLine("Key = {0}, Value = {1}",kv.Key, kv.Value);
}
}
在C# 2.0中,对比C# 1.1来说,新版本增加了很多新特性,其中最重要的是对泛型的支持。通过泛型,我们可以定义类型安全的数据结构,而无需使用实际的数据类型。这能显著提高性能并得到更高质量的代码。泛型并不是什么新鲜的东西,他在功能上类似于C++的模板,模板多年前就已存在C++上了,并且在C++上有大量成熟应用。
本文讨论泛型使用的一般问题,比如为什么要使用泛型、泛型的编写方法、泛型中数据类型的约束、泛型中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握泛型的一般应用,编写出更简单、通用、高效的应用系统。
什么是泛型
我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。
为什么要使用泛型
为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:
public class Stack
{
private int[] m_item;
public int Pop(){...}
public void Push(int item){...}
public Stack(int i)
{
this.m_item = new int[i];
}
}
上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:
public class Stack
{
private object[] m_item;
public object Pop(){...}
public void Push(object item){...}
public Stack(int i)
{
this.m_item = new[i];
}
}
这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:
当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。
在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。
在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):
Node1 x = new Node1();
stack.Push(x);
Node2 y = (Node2)stack.Pop();
上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。
针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。
使用泛型
下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:
public class Stack<T>
{
private T[] m_item;
public T Pop(){...}
public void Push(T item){...}
public Stack(int i)
{
this.m_item = new T[i];
}
}
类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:
//实例化只能保存int类型的类
Stack<int> a = new Stack<int>(100);
a.Push(10);
a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据
int x = a.Pop();
//实例化只能保存string类型的类
Stack<string> b = new Stack<string>(100);
b.Push(10); //这一行编译不通过,因为类b只接收string类型的数据
b.Push("8888");
string y = b.Pop();
这个类和object实现的类有截然不同的区别:
1. 他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。
2. 无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。
3. 不需要类型转换。