C# 泛型
泛型的是什么?
先上两段代码看看:
需求1:比较两个int值的大小
public class Compare { // 返回两个整数中大的一个 public static int Compareint(int int1, int int2) { if (int1.CompareTo(int2) > 0) { return int1; } return int2; } }
需求2:比较两个字符串的大小
public static string Comparestring(string str1, string str2) { if (str1.CompareTo(str2) > 0) { return str1; } return str2; }
两段代码的共性:只是比较的类型不一样的,实现方式是完全一样
对于程序猿来说毫无意义的重复造轮子是不能忍的!
我们希望有一种类型是通用的,可以把任何类型当做参数传入到这个类型中去实例化为具体类型的比较,微软大大也想到了这个问题,所以在C#2.0中添加了泛型这个新的特性。
简言之:泛型就是--通用类型,实现类型参数化
接下来我们把上面的两个方法改造成适用于通用类型的方法(泛型方法)
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; } } }
调用方式如下:
public class Program { static void Main(string[] args) { Console.WriteLine(Compare<int>.CompareGeneric(3, 4)); Console.WriteLine(Compare<string>.CompareGeneric("abc", "a")); Console.Read(); } }
由上面的例子我们可以看出泛型的一些优点
优点一:代码重用(算法重用)
继续上代码:
static void Main(string[] args) { Stopwatch stopwatch = new Stopwatch(); // 非泛型数组 ArrayList arraylist = new ArrayList(); // 泛型数组 List<int> genericlist= new List<int>(); // 开始计时 stopwatch.Start(); for (int i = 1; i < 10000000; i++) { ////genericlist.Add(i); arraylist.Add(i); } // 结束计时 stopwatch.Stop(); // 输出所用的时间 TimeSpan ts = stopwatch.Elapsed; string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds/10); Console.WriteLine("Excute Time:" + elapsedTime); Console.Read(); }
使用非泛型集合arraylist执行所需时间:
将上面代码换成泛型集合genericlist之后:
优点二:执行效率高(避免装箱)
当我们向这个泛型genericlist数组中添加string类型的值时,此时就会造成编译器报错 “无法从“string”转换为’int‘ ”
优点三:类型安全
泛型的表现形式
1.泛型类型:类、接口、委托、结构
public class Transform<T,S> public interface IConvertType<TSource, TTarget> public delegate void Caculate﹤T﹥(T item) public struct Nullable<T> where T : struct
2. 泛型方法
解释几个概念:
类型参数:类型占位
类型实参:实际调用时需要的类型,替换掉类型参数
开放类型:具有类型参数的类型就是开放类型(所有未绑定泛型类型都属于开放类型)
封闭类型:为每个类型参数都传递了实际的数据类型(类型实参)
// 声明开放泛型类型 public sealed class DictionaryStringKey<T> : Dictionary<string, T> { } static void Main(string[] args) { object o = null; // Dictionary<,>是一个开放类型,它有2个类型参数 Type t = typeof(Dictionary<,>); // 创建开放类型的实例(创建失败,出现异常) o = CreateInstance(t); Console.WriteLine(); // DictionaryStringKey<>也是一个开放类型,但它有1个类型参数 t = typeof(DictionaryStringKey<>); // 创建该类型的实例(同样会失败,出现异常) o = CreateInstance(t); Console.WriteLine(); // DictionaryStringKey<int>是一个封闭类型 t = typeof(DictionaryStringKey<int>); // 创建封闭类型的一个实例(成功) o = CreateInstance(t); Console.WriteLine("对象类型 = " + o.GetType()); Console.Read(); } // 创建类型 private static object CreateInstance(Type t) { object o = null; try { // 使用指定类型t的默认构造函数来创建该类型的实例 o = Activator.CreateInstance(t); Console.WriteLine("已创建{0}的实例", t.ToString()); } catch(Exception ex) { Console.WriteLine(ex.Message); } return o; }
运行结果为(从结果中也可以看出开放类型不能创建该类型的一个实例,异常信息中指出类型中包含泛型参数):
类型推断:编译器会在调用一个泛型方法时自动判断要使用的类型
注意:泛型的类型推断只适用于泛型方法,不适用于泛型类型
static void Main(string[] args) { int n1 = 1; int n2 = 2; // 如果没有类型推断时需要写的代码 // GenericMethodTest<int>(ref n1, ref n2); // 有了类型推断后需要写的代码 // 此时编译器可以根据传递的实参 1和2来判断应该使用Int类型实参来调用泛型方法 // 可以看出有了类型推断之后少了<>,这样代码多的时候可以增强可读性 //string t1 = "123"; //object t2 = "456"; //// 此时编译出错,不能推断类型 //// 使用类型推断时,C#使用变量的数据类型,而不是使用变量引用对象的数据类型 //// 所以下面的代码会出错,因为C#编译器发现t1是string,而t2是一个object类型 //// 即使 t2引用的是一个string,此时由于t1和t2是不同数据类型,编译器所以无法推断出类型,所以报错。 //GenericMethodTest(ref t1, ref t2); } // 类型推断的Demo private static void GenericMethodTest<T>(ref T t1,ref T t2) { T temp = t1; t1 = t2; t2 = temp; }
泛型类型约束
1.引用类型约束:确保类型实参是引用类型(类,接口,数组,委托)
class RefClass<T> where T : class
2.值类型约束:确保类型实参是值类型(int, decimal,float,long)
class ValClass<T> where T : struct
3.构造函数类型约束:检查类型实参是否具有可用于创建类型实参的公共无参构造函数适用于所有值类型
public T CreateInstance<T> where T : new()
4.转换类型约束:类型实参必须可以通过一致性,引用或装箱隐式的转换为该类型
class Sample<T> where T : Stream Sample<Stream>一致性转换 class Sample<T> where T : IDisposable Sample<SqlConnection>引用转换 class Sample<T> where T : IComparable Sample<int> 装箱转换
5.组合约束:可以将不同种类的约束合并到一起作为泛型类型的约束
class Sample<T> where T : class, IDisposable, new() class Sample<T,U> where T : Stream where U : IDisposable
无效的约束组合:
class Sample<T> where T: class, struct (没有任何类型即时引用类型又是值类型的,所以为无效的) class Sample<T> where T: Stream, class (引用类型约束应该为第一个约束,放在最前面,所以为无效的) class Sample<T> where T: new(), Stream (构造函数约束必须放在最后面,所以为无效) class Sample<T> where T: IDisposable, Stream(类必须放在接口前面,所以为无效的) class Sample<T,U> where T: struct where U:class, T (类型形参“T”具有“struct”约束,因此“T”不能用作“U”的约束,所以为无效的) class Sample<T,U> where T:Stream, U:IDisposable(不同的类型参数可以有不同的约束,但是他们分别要由一个单独的where关键字,所以为无效的)
下面的代码可以编译通过吗?
// 比较两个数的大小,返回大的那个 private static T max<T>(T obj1, T obj2) { if (obj1.CompareTo(obj2) > 0) { return obj1; } return obj2; }
答案是不能,应该写成下面这样:
private static T max<T>(T obj1, T obj2) where T:IComparable<T> { if (obj1.CompareTo(obj2) > 0) { return obj1; } return obj2; }
原因是:我们需要指定一个类型约束,让C#编译器知道这个类型参数一定会有CompareTo方法
泛型的可变性:
面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类型,此时就存在一个隐式的转化——从FileStream类型(子类引用)——>Stream类型(父类引用,引用类型的数组也存在这种从子类引用——>父类引用的转化,例如string[] 可以转化为object[],这样的代码是可以通过编译。
协变:输出的类型参数可以从一个派生类转换为基类,用关键字out来标注
public class Animal { public virtual void Write() { Console.WriteLine("这是基类"); } } public class Dog : Animal { public override void Write() { Console.WriteLine("这是派生类小狗"); } } //为了让派生类Dog隐式转换成基类Animal,先定义支持协变的泛型接口。 //支持协变的接口 public interface IFactory<out T> { T Create(); } //实现这个接口: public class Factory<T> : IFactory<T> { public T Create() { return (T)Activator.CreateInstance<T>(); } } //客户端调用: class Program { static void Main(string[] args) { IFactory<Dog> dogFactory = new Factory<Dog>(); IFactory<Animal> animalFactory = dogFactory; //协变 Animal animal = animalFactory.Create(); animal.Write(); Console.ReadKey(); } }
逆变:输入的类型参数可以从一个基类隐式转换为派生类,C#4.0中引入in关键字来标 记泛型参数支持逆变性
var actionAnimal = new Action<Animal>(a => Console.WriteLine("animal base")); Action<Dog> actionDog = actionAnimal; actionDog(new Dog());
注意:
1. 只有接口和委托支持协变和逆变(如Func<out TResult>,Action<in T>),类或泛型方法的类型参数都不支持协变和逆变。
2. 协变和逆变只适用于引用类型,值类型不支持协变和逆变(因为可变性存在一个引用转换,而值类型变量存储的就是对象本身,而不是对象的引用),所以List<int>无法转化为IEnumerable<object>.
3. 必须显示用in或out来标记类型参数。
4. 委托的可变性不要再多播委托中使用
Func<string> stringfunc = () => ""; Func<object> objectfunc = () => new object(); Func<object> combined = stringfunc + objectfunc;
上面代码可以通过编译,因为泛型Func<out T>支持协变,所以将Func<string>转换为Func<object>类型,但是对象本身仍然为Func<string>类型,然而Delegate.Combine方法要求参数必须为相同类型——否则该方法无法确定要创建什么类型的委托(是Func<string>类型呢还是Func<object>?),所以上面代码在运行时会抛出ArgumetException(错误信息为——委托必须具有相同的类型)