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 领域

 

原创粉丝点击