为什么使用泛型
- 内存的分配:在堆中分配内存空间来存放复制的实际数据
- 完成实际数据的赋值:将值类型实例的实际数据复制到新分配的内存中。
- 地址返回:将堆中的独享地址返回给引用类型变量
拆箱:将引用类型转换成值类型
- 检查实例:首先检查进行拆箱操作的引用类型是否为null,如果为null就抛出异常,如果不为null则继续检查变量是否和拆箱后的类型是同一类型。 注意:被装箱过的数据才可以拆箱
-
地址返回:返回已装箱变量的实际数据部分的地址
-
数据复制:将堆中实际数据复制到栈中
class Program
{
public static void Main()
{
string a="hello";
fun(ref a);//按引用进行传递,传递的是a的地址,在函数中通过形参str直接修改实参a;
Console.WriteLine(a);
}
public static void fun(ref string str)
{
str="world";//如果不按引用传递,改变的是引用的指向,并没有影响实参
}
}
out 关键字 在使用时out声明的形参必须赋初值,out就是讲方法中的值带回到主函数中,将值赋值给形参public static void Main()
{
int a=10;
fun(out a);//out 关键字,必须要通过形参给实参做赋值操作
Console.WriteLine(a);
}
public static void fun(out int n)
{
//Console.WriteLine(n);
n = 100;//out关键字声明的形参必须要先初始化才能操作
Console.WriteLine(n);
}
这是简单的变量的装箱拆箱操作,下面说一下方法
普通的方法中,定义的参数是什么类型就只能传递什么类型的参数。
首先 定义的是什么类型的数组就存什么类型
public void add(int v)
{
array[size++]=v; //这样就只能添加int类型的数据
}
然后你会想到用一个通用的类型数据object
public void add(object v)
{
array[size++]=v;
}
使用object可以解决数据类型的问题,但是在会出现装箱、拆箱操作,这将在堆上分配和回收大量的变量,若数据量大,性能会
损失非常严重,在处理引用类型时虽然没有装箱和拆箱操作,但是将数据类型的强制转换操作,增加处理器的负担。
这里就可以使用一段代码测试一下统计一下两种操作的时间。(直接复制下来在你的机器上运行一下)
using System;
using System.Diagnostics;//统计时间的命名空间
namespace aa
{
class Program
{
public static void Main()
{
int max = 10000000;
Vector1 v1 = new Vector1(max);
Stopwatch watch1 = new Stopwatch();
watch1.Start();
for (int i = 0; i < max; i++)
{
v1.add(i);
}
watch1.Stop();
TimeSpan span1 = watch1.Elapsed;
Console.WriteLine("{0}:{1}",span1.Seconds,span1.Milliseconds);
Vector2<int> v2 = new Vector2<int>(max);
Stopwatch watch2 = new Stopwatch();
watch2.Start();
for (int i = 0; i < max; i++)
{
v2.add(i);
}
watch2.Stop();
TimeSpan span2 = watch2.Elapsed;
Console.WriteLine("{0}:{1}",span2.Seconds,span2.Milliseconds);
}
}
public class Vector1
{
private object []arrary;
private int size;
public Vector1(int n)
{
arrary = new object[n];
size = 0;
}
public object this[int index]
{
get{ return arrary[index];}
set{ arrary[index] = value;}
}
public void add(object obj)
{
arrary[size++] = obj;
}
}
public class Vector2<T>
{
private T[]arr;
private int size;
public Vector2(int n)
{
arr = new T[n];
size = 0;
}
public T this[int index]
{
get{ return arr[index];}
set{ arr[index] = value;}
}
public void add(T x)
{
arr[size++] = x;
}
}
}
使用泛型的优点
使用泛型就可以有传递多种类型的参数,减少了相同方法的重复定义。不仅可以解决类型问题还可以很大程度的优化性能
Vector<int> v1 = new Vector<int>(5);
for (int i = 0; i < 5; i++)
{
v1.add(i);
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(v1[i]);
}
Vector<double> v2 = new Vector<double>(5);
for (int i = 0; i < 5; i++)
{
v2.add(i * 1.1d);
Console.WriteLine(v2[i]);
}
1.他是类型安全的,实例化了int 类型的栈,就不能处理string类型,其他数据类型也是一样
2.无需装箱和拆箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码类型已经确定,所以无需装箱和拆箱。
3.无需类型转换。
4.泛型将方法实现行为与方法操作的数据类型分离,实现了代码重用。
泛型中的两个容器- List<>容器
List<>容器中每个元素都对应着一个整型下标。自己创建容器时都要自己定义一个长度,但是list容器不需要我们自己传递参数
系统会自动为我们定义,并且会自动扩展容量(List中包含多个属性和方法自己在编辑器中查看就行)
2.Dictionary 容器 以键值对的形式存储数据
每个元素(值)都对应着一个下标(键)。包含两个类型 键的类型和值的类型
foreach(KeyValuePair<string,int>i in d) 使用这样的方式遍历字典中的数据
Dictionary<string ,int> d = new Dictionary<string, int>();
//通过键来访问对应的值
for (int i = 0; i < 5; i++)
{
d.Add("hel" + i, i);
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(d["hel"+i]);
}
public class Channel
{
//这个是Channel的标题
private string tittle;
//创建了一个存放News 的字典
private Dictionary<string,News>newsList;
//创建一个索引,就可以通过channel对象直接访问 channel c1["aaa"]直接访问
//不用再使用c1.newslist["aaa"];
public News this[string index]
{
get{ return newsList[index];}
set{ newsList[index] = value;}
}
public Channel(string _tittle)
{
tittle = _tittle;
newsList = new Dictionary<string, News>();
}
//调用系统的add方法将新闻添加到newslist中,然后每一个channel中都有一个词典,
public void addNews(News n)
{
newsList.Add(n.Tittle, n);
}
}
泛型方法,缩小了泛型的定义,只在方法中使用泛型
另外T只是一个表示符,可以使用其他的字母,不是非得使用T ,只是T 已经成了一种公认的习惯
泛型约束
泛型约束
通过where关键字给T添加约束
1.基类约束
public class Vector<T> where T:Person
{}
2.接口约束
给T 指定了类型必须继承接口中所有的方法
只有继承了接口了的实体类才能作为泛型参数
3.new()构造函数约束
允许开发人员实例化一个泛型类型的对象new()约束要求类型实参必须提供一个无参数的公有构造函数。使用new()约束时,
可以通过调用该无参数的构造函数来创建对象。
(1) new()约束可以与其他约束一起使用,但必须位于约束列表的末端。
(2) 仅允许使用无参的构造函数创建对象,即使同时存在其他的构造函数也是如此
(3) 不可以同时使用new()约束和值类型约束。因为值类型都隐式的提供了一个无参公共构造函数。
4.引用类型约束
where T:class 这时T 必须是引用类型
5.值类型约束
where T :struct 这是T必须是值类型。
不能同时出现的约束类型不能同时出现的约束:基类约束,值类型约束、引用类型约束 构造函数和值类型不能一起用
多个约束时的顺序
顺序 第一位(基类型约束,值类型约束,引用类型约束)第二位 接口约束 第三位 构造函数约束
泛型接口
泛型接口,在继承接口时就要确定接口的类型
public interface IAdd<T>
{
void add(T x);
}
public class Vector1:IAdd<int>//接口继承前要指定泛型参数
{
}
泛型接口中的泛型参数可以使用泛型类中的参数
public class Vector2<T>:IAdd<T>
{
}
泛型委托
public delegate T Mydelegate<T> (T x,T y);
泛型委托的优点在于,他允许开发人员以类型安全的方式定义一种通用形式,可用于匹配任意兼容的方法
泛型方法重载,参数类型一样时就会出现问题,因为方法名一样,参数列表也一样
泛型方法中的泛型参数和类中的泛型参数是没有关系的可以不一致