002-用泛型实现参数化类型
说起泛型我们简直太熟悉了,在unity中最直观的就是dictionary的使用了,我们只是知道dictionary是一个键值对,但是并不知道这个为什么用?在有的时候为什么往往是更重要的。接下来我们来看看一下使用泛型与没有使用泛型的时间消耗。
//用泛型的方法 private static void TestGeneric() { Stopwatch stopwatch = new Stopwatch(); List<int> list1 = new List<int>(); stopwatch.Start(); for (int i = 0; i < 10000000; i++) { list1.Add(i); } stopwatch.Stop(); TimeSpan timeSpan = stopwatch.Elapsed; string elapedTime = String.Format("{0:00}:{1:00}:{2:00}:{3:00}", timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds, timeSpan.Milliseconds ); Console.WriteLine("用泛型的运行时间:" + elapedTime); } //没有使用泛型的方法 private static void TestNoGeneric() { Stopwatch stopwatch = new Stopwatch(); ArrayList arrayList = new ArrayList(); stopwatch.Start(); for (int i = 0; i < 10000000; i++) { arrayList.Add(i); } stopwatch.Stop(); TimeSpan timeSpan = stopwatch.Elapsed; string elapedTime = String.Format("{0:00}:{1:00}:{2:00}:{3:00}", timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds, timeSpan.Milliseconds); Console.WriteLine("没有使用泛型运行时间:" + elapedTime); } static void Main(string[] args) { TestGeneric(); TestNoGeneric(); Console.ReadKey(); }
图片为:
结果是一目了然,同样是向集合中添加1000万的数据,但是用的时间却是相差这么多,这是什么原因呢?我们知道方法还是用一般的方法来表示比较好,所以我们在没有泛型的情况下通常会使用object类型了,但是这个object类型一点不好,我们在前面知道用这个基类,会有大量的装箱与拆箱操作,这个就是为什么没有泛型的集合会有这么长的时间了。对于C#2的最重要的特性就是泛型的加入了,泛型的作用就是实现代码的重用,减少装箱与拆箱的过程,泛型是避免损失的有效方法;而且可以见扫CPU的损耗。或者我们可以这样理解泛型就是强制你把类型固定,但是这个固定只是发生在你需要它的时候,一般情况下还是一般方法的。
说真的,dictionary真的没有什么好说的,接下来我么来看看泛型的其他特性。
1在前面是只是说了,我们为什么要使用泛型,接下来我们来看看泛型的定义:
public class Compare<T> where T : IComparable { public static T compareGeneric(T t1, T t2) { if (t1.CompareTo(t2) > 0) return t1; else return t2; } } class Program { static void Main(string[] args) { Console.WriteLine(Compare<int>.compareGeneric(21, 45)); Console.ReadKey(); } }
这个写法也是很简单的,对类型进行泛型的定义,然后所有T的类型就是int类型了。
2 泛型的类型参数
泛型分为开放泛型与封闭泛型。其中开放类型是指包含类型参数的泛型,但是没绑定类型;封闭类型是指已经为每一个类型参数都传递了一数据类型。这个怎么说呢?还是dictionary类了,当你没有使用的时候可以近似的看作是开放类型,当你使用了就是封闭类型了。
//泛型参数 public class DictionaryString<T> : Dictionary<string, T> { } public class Program { static void Main(string[] args) { Type t = typeof(Dictionary<,>); Console.WriteLine("是否为开放类型:" + t.ContainsGenericParameters); t = typeof(DictionaryString<int>); Console.WriteLine("是否为开放类型:" + t.ContainsGenericParameters); Console.ReadKey(); } }
这个结果和我们说的一样,就是你没有进行具体的类型赋值,这个就是开放类型;如果你进行了具体的类型赋值的话,这个就是封闭类型。我们在用的时候还是封闭类型用的比较多一点,但是我们还是要知道这个到底是什么一回事情。
接下来还有静态数据类型与泛型的关系。静态数据类型是属于类型的,如果定义了一个静态字段x,则不管之后创建了多少个改该类的实例,也不管派生多个实例,都只存在一个。泛型并非如此,每个封闭泛型都具有自己的静态数据。这个部分我们只是做一个简单的说明,其实我们也是知道的在C#中静态变量或者方法只是存在一个的,它一旦进行就会值存在一个。
3泛型方法和判断泛型声明
我们一定不能把泛型想的太复杂了,泛型方法归根到底就是方法,它只是把方法一般化了。有的时候,我们需要将泛型T的作用范围缩小,直接将其应用在方法上,这样的话能缩小范围。这就是泛型方法。
public class GeneMethod { public GeneMethod() { } public static T Max<T>(T value) { Console.WriteLine("value={0}", value); return value; } } class Program { static void Main(string[] args) { Console.WriteLine(GeneMethod.Max<int>(24)); Console.ReadKey(); } }
通过上面的代码我们知道泛型方法就是方法,只是将参数全部泛型化,你需要做的就是将泛型参数全被实参化。
4类型参数约束
我们已经知道泛型方法的写法了,但是我们却发现了一个问题,他就是我们将这个参数泛型化,我们就是在特定的地方将这个参数实参话,但是总是有一点不好的,因为我们知道参数是有很多的类型的,比如说类、枚举、结构体等,正如有一句话说的一样,改编不是胡编,戏说不是胡说。我们需要给泛型加一个限制。这个限制就是类型限制了。
限制的类型分类有很多,但是我们只说两种,因为多说无益,其他也用不到。接口类型约束与构造函数约束。
我们在前面的比较例子上已经写过了 where T:IComparable,其中where语句就是用来使类型继承与IComparable接口,从而对其进行约束。这个就是接口类型约束。
where T:new()这个即时构造函数约束。new()构造函数约束允许开发人员实例化一个泛型类型的对象。new()约束要求类型参数必须提供一个无参数的共有构造函数。使用new()约束时,可以通过调用该无参数的构造函数来创建对象。
public class SingleTon<T> where T:new () { private static T _instance; public static T Instance { get { if(_instance==null) { _instance = new T(); } return _instance; } } public static T GetInstance() { if (_instance == null) { _instance = new T(); } return _instance; } public void DebugLog() { Console.WriteLine("Hello World"); } } public class Person : SingleTon<Person> { public void PersonPring() { Console.WriteLine("你好"); } } class Program { static void Main(string[] args) { Person.Instance.DebugLog(); Person.GetInstance().PersonPring(); Console.ReadKey(); } }
在上面的代码中我们可以看到singleton是一个泛型类型,这个类型并没有指定类型,这个就需要你重新要创建一个类去实参它,然后我们就会在main方法中去调用它。有一点要注意的是,我们可以在泛型类型写一个没有参数的构造函数,也可以不用写,如果你有参构造函数的话,就一定需要一个无参构造函数了,这个是new中定义的。
5 泛型委托
泛型委托就是将委托泛型化。这样说也是很简单的,因为你无论是什么样的委托,它都是委托,他都会遵守委托的定义。我们还是先看一下泛型委托的代码吧。
public delegate void Mydelegate<T>(T value); class Program { public static void Method1(int num) { Console.WriteLine("Method1:"+num); } public static void Method2(Mydelegate<string> d) { d("hello world"); } public static void print(string str) { Console.WriteLine("str:" + str); } static void Main(string[] args) { //方法一 Mydelegate<int> d; d = new Mydelegate<int>(Program.Method1); d(1234); //方法二 Program.Method2(Program.print); Console.ReadKey(); } }
从上面的写法上来看,泛型委托只是将委托参数泛型化。在你使用的话,需要将泛型参数实参化。我们在上面写了两种的方式,一个是将委托先进行实例化,接着在在指定方法,用委托去引用一个方法,接着我们直接在这个委托中写参数就行了,因为我们知道委托的实质就是间接完成某些操作,我们将参数给这个委托变量的话,它会去找与它进行关联的方法,然后将自己得到的参数再给这个方法的,最后由这个方法进行操作的完成。其实我们在这里已经知道委托的好处了,我们在编程的时候将相同的方法写在一起,然后我们在别的地方进行调用的话,只需要用委托进行调用了,这样做的话就会减少代码的耦合性。我们在上面的代码中我们还看见了第二种方法这个是直接用类+静态方法的形式来完成的,这样做的好处是什么呢?我们可以直接用类+静态方法的形式去调用方法,这样我们就不用去构造类了,我们直接在这个方法中组申明一个委托变量,这样的话我们我们就可以将一个函数当作一个参数来传递,其实也是这样的话,委托就将方法进行传递的我们在这个方法中得到来自委托传递的参数,就是这样方法,我们就可以在这里去添加参数,参数会通过委托去传递给原来那个关联的方法,这样就可以完成相关的操作了。还是那句话,委托就是间接去执行操作。因为委托是引用传递的方法,所以一处动处处动,这个与值传递的方式是不一样的。接下来是结果:
6 泛型的重载
其实这个一点都不奇怪,因为普通的方法都可以进行重载,那泛型方法一定是可以进行重载的,他们的原理都是一样的,函数名可以一样,但是参数的格式不一样就行了,这个在泛型方法的重载也是相同的。我们接下来还是来看看例子吧:
public class TestClass<T,V> { public T Add(T value1, V value2) { Console.WriteLine("执行了第一个重载方法"); return value1; } public T Add(V value1,T value2) { Console.WriteLine("执行了第二个重载方法"); return value2; } public int Add(int a, int b) { Console.WriteLine("执行了第三个重载方法"); return a + b; } } class Program { static void Main(string[] args) { TestClass<int, int> test = new TestClass<int, int>(); Console.WriteLine(test.Add(12, 35)); Console.ReadKey(); } }
结果为:
从上面的结果我们可以看到虽然方法的名字都是一样的,但是知道其中的参数不一样就行了,系统就可以进行判断了,但是我们如果参数的形式都一样的话,系统就不能分辨了,因为这样的话,代码中就会有两个相同的方法了,重载方法的本质并不是相同的方法,相同的方法就会有错误了,所以我们,只要参数不一致,重载泛型就可以使用了。
最后来个小洁,泛型是c#2中很重要的一个特性,它是将方法一般化,但是又加强了约束,看似自相矛盾的作用,却是泛型的本质。在准备使自由,在使用时约束,这个是时间上的联系。
好了我们的泛型部分就到这里了,接下来世可空类型---NULL。