c#回顾之泛型编程
来源:互联网 发布:部落冲突七本满防数据 编辑:程序博客网 时间:2024/05/17 15:36
C#泛型编程
泛型用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。减少装箱拆箱的麻烦,和在托管堆上分配和回收大量的变量。
错误案例:string s=“sd”;
Object obj=(object)s;
int z=(int)obj; //这里是错误的,编译器不会报错
这个类和object实现的类有截然不同的区别:
1. 他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。
2. 无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。
3. 无需类型转换。
泛型类实例化的理论
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:
List<string> 和List<int> 是不同的两个类
泛型类中数据类型的约束
publicclass Node<T, V> where T : Stack, IComparable
where V: Stack
{...}
以上的泛型类的约束表明,T必须是从Stack和IComparable继承,V必须是Stack或从Stack继承,否则将无法通过编译器的类型检查,编译失败。
通用类型T没有特指,但因为C#中所有的类都是从object继承来,所以他在类Node的编写中只能调用object类的方法,这给程序的编写造成了困难。比如你的类设计只需要支持两种数据类型int和string,并且在类中需要对T类型的变量比较大小,但这些却无法实现,因为object是没有比较大小的方法的。 了解决这个问题,只需对T进行IComparable约束,这时在类Node里就可以对T的实例执行CompareTo方法了。这个问题可以扩展到其他用户自定义的数据类型。
如果在类Node里需要对T重新进行实例化该怎么办呢?因为类Node中不知道类T到底有哪些构造函数。为了解决这个问题,需要用到new约束:
public class Node<T, V> where T : Stack,new()
where V: IComparable
需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。
C#中数据类型有两大类:引用类型和值类型。引用类型如所有的类,值类型一般是语言的最基本类型,如int, long, struct等,在泛型的约束中,我们也可以大范围地限制类型T必须是引用类型或必须是值类型,分别对应的关键字是class和struct:
public class Node<T, V> where T : class
where V: struct
注意泛型中的new约束不能和struct单独使用
泛型方法
泛型不仅能作用在类上,也可单独用在类的方法上,他可根据方法参数的类型自动适应各种参数,这样的方法叫泛型方法。看下面的类:
public class Stack2
{
public voidPush<T>(Stack<T> s, params T[] p)
{
foreach (T t in p)
{
s.Push(t);
}
}
}
泛型委托
public class MyClass<T>
{
public delegate voidGenericDelegate(T t);
public void SomeMethod(Tt)
{ }
}
public GenericMethodDemo()
{
MyClass<int> obj =new MyClass<int>();
MyClass<int>.GenericDelegate del;
del = newMyClass<int>.GenericDelegate(obj.SomeMethod);
del(3);
}
泛型中的静态成员变量
在C#1.x中,我们知道类的静态成员变量在不同的类实例间是共享的,并且他是通过类名访问的。C#2.0中由于引进了泛型,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。
这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,比如:
Stack<int> a = new Stack<int>();
Stack<int> b = new Stack<int>();
Stack<long> c = new Stack<long>();
类实例a和b是同一类型,他们之间共享静态成员变量,但类实例c却是和a、b完全不同的类型,所以不能和a、b共享静态成员变量。
泛型中的静态构造函数
静态构造函数的规则:只能有一个,且不能有参数,他只能被.NET运行时自动调用,而不能人工调用。
泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:
1. 特定的封闭类第一次被实例化。
2. 特定封闭类中任一静态成员变量被调用。
泛型类中的方法重载
方法的重载在.Net Framework中被大量应用,他要求重载具有不同的签名。在泛型类中,由于通用类型T在类编写时并不确定,所以在重载时有些注意事项,这些事项我们通过以下的例子说明:
public class Node<T, V>
{
public T add(T a, V b) //第一个add
{
return a;
}
public T add(V a, T b) //第二个add
{
return b;
}
public int add(int a, int b) //第三个add
{
return a + b;
}
}
上面的类很明显,如果T和V都传入int的话,三个add方法将具有同样的签名,但这个类仍然能通过编译,是否会引起调用混淆将在这个类实例化和调用add方法时判断。请看下面调用代码:
Node<int, int> node = new Node<int,int>();
object x = node.add(2, 11);
这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。
Node<string, int> node = newNode<string, int>();
object x = node.add(2, "11");
这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。
由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:
当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。
泛型类的方法重写
方法重写(override)的主要问题是方法签名的识别规则,在这一点上他与方法重载一样,请参考泛型类的方法重载。
泛型的使用范围
本文主要是在类中讲述泛型,实际上,泛型还可以用在类方法、接口、结构(struct)、委托等上面使用,使用方法大致相同。
interface Igenerics<X,Y>whereX:struct
where Y:class
{
void add(int x,int y);
}
classGenericst<T,K> where T:struct,Igenerics<T,K>
where K:class,new()
{…}
实列说明
首先定义一个接口约束Igenerics .cs:
interface Igenerics<X,Y>where X:class
where Y:class
{
string Name { get; set; }
void add(int x,int y);
}
接着定义一个泛型类:
class Genericst<T, K>
//约束T第一个必须为引用类型,且继承接口 Igenerics
where T : class, Igenerics<string,string>
//这里多加了一个约束必须有无参构造函数,new必须反最后
where K : class, Igenerics<string, string>,new()
{
T t1;
K k1;
//每次声明时加一
public static int index = 0;
public Genericst(T t,K k):this(t)
{
this.k1 = k;
}
public void showName()
{
Console.WriteLine("print T's name:{0}", k1.Name);
}
public Genericst(T t)
{
this.t1 = t;
k1 = new K();
Console.WriteLine("第{0}次出现这种类型",index);
index++;
}
public void getT<T>()
{
Console.WriteLine("this is a {0}'s data",typeof(T).FullName);
}
}
定义两个用来满足泛型的约束:
///<summary>
///满足泛型中的K的约束
///</summary>
class generstr : Igenerics<string, string>
{
public string Name { get; set; }
public void add(int x, int y)
{
Console.WriteLine("this function \"classgenercla add(int x,int y)\":result{0}", x + y);
}
public generstr()
{
Name = "name";
}
public generstr(string name)
{
this.Name = name;
}
}
///<summary>
///满足泛型中的T的约束
///</summary>
class genercla : Igenerics<string, string>
{
public string Name { get; set; }
public void add(int x, int y)
{
Console.WriteLine("this function \"classgenercla add(int x,int y)\":result{0}", x + y);
}
}
最后结果是:
static void Main(string[] args)
{
Genericst<genercla, generstr>genericst = new Genericst<genercla, generstr>(new genercla(),new generstr("riven"));
Genericst<genercla, generstr>genericst2 = new Genericst<genercla, generstr>(new genercla(),new generstr("riven"));
Genericst<genercla, genercla>genericst3 = new Genericst<genercla, genercla>(new genercla(),new genercla());
genericst.getT<System.IO.Stream>();
genericst.showName();
}
运行结果:
F:\泛型回顾\泛型回顾>dotnet run
第0次出现这种类型
第1次出现这种类型
第0次出现这种类型
this is a System.IO.Stream's data
print T's name:riven
小结
C# 泛型是开发工具库中的一个无价之宝。它们可以提高性能、类型安全和质量,减少重复性的编程任务,简化总体编程模型,而这一切都是通过优雅的、可读性强的语法完成的。尽管 C# 泛型的根基是 C++ 模板,但C# 通过提供编译时安全和支持将泛型提高到了一个新水平。C# 利用了两阶段编译、元数据以及诸如约束和一般方法之类的创新性的概念。毫无疑问,C# 的将来版本将继续发展泛型,以便添加新的功能,并且将泛型扩展到诸如数据访问或本地化之类的其他 .NET Framework 领域
- c#回顾之泛型编程
- C#数据结构回顾之单链表
- C#数据结构回顾之顺序表
- C#数据结构回顾之顺序栈
- C#数据结构回顾之链栈
- C#数据结构回顾之循环队列
- 黑马程序员---回顾之java网络编程
- 回顾我走过的编程之路
- 18Java语法回顾之网络编程
- 回顾微软编程之美2016复赛
- 回顾十二年编程之路
- C#回顾学习笔记三十五:泛型集合List
- 回顾c#
- C#回顾
- C#回顾
- C#回顾
- C#回顾
- 深入.NET平台和C#编程第一章回顾
- C#和WPF开发中的多线程
- Hbase API
- LWIP使用经验---变态级(好文章)
- 逆向工程
- oracle的游标
- c#回顾之泛型编程
- 【OpenCV】边缘检测
- Django Reverse的使用
- 闪亮亮
- [torch]maskzero-brnn
- 10 Wget Command Examples
- Java读取txt文件和写入txt文件
- hibernate.hbm2ddl.auto设置为updata时需要注意的地方
- Kafka 0.10.0 SASL/PLAIN身份认证及权限实现