C# 泛型编程之概述、default()方法、别名指定与泛型约束

http://blog.163.com/chenhui_761217/blog/static/340737792010912103043831/

如何理解 default() 方法:

之所以会用到default关键字,是因为需要在不知道类型参数为值类型还是引用类型的情况下,为对象实例赋初值。考虑以下代码:

class TestDefault<T>
    {
        public T foo()
        {
            T t = null; //???
            return t;
        }
    }

如果我们用int型来绑定泛型参数,那么T就是int型,那么注释的那一行就变成了 int t = null;显然这是无意义的。为了解决这一问题,引入了default关键字:

class TestDefault<T>
    {
        public T foo()
        {
                return default(T);
        }
    }

-----------------------以下是原文--------------------------

泛型概述

    首先我们看一个在泛型出现之前通用的数据结构示例

    通用数据结构类

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public class Stack
{
    
object[] m_Items;
    
public Stack()
    { }
    
public void Push(object item)
    { }
    
public object Pop()
    { }
}

   调用实例

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public partial class _Default : System.Web.UI.Page 
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Stack stack 
= new Stack();
        stack.Push(
1);//装箱
        stack.Push(2);//装箱
        int number = (int)stack.Pop();//拆箱
    }
}

    然后我们看看基于上面object解决方案存在的问题

    1.由于在实际调用的时候会频繁出现值类型的装箱、拆箱,这将会导致很多垃圾碎片,增加垃圾收集的负担;对于引用类型这方面的问题也不容小觑

    2.编译时任务类型都可以转换成object,无法保证运行时类型的安全。请看下面代码所示,编译可通过,运行时报错:

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public partial class _Default : System.Web.UI.Page 
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Stack stack 
= new Stack();
        stack.Push(
"test");

        
//这句显然是错误的,但编译可以通过
        int number = (int)stack.Pop();
    }
}

    那么有人提出了,既然通用数据结构类用object类型不行,那我可以用特定类型来替换上面结构类中的object类型,来处理特定类型的Stack,这样虽然解决了上面两个问题,但又会出现如下问题:

    1.影响工作效率。

         因为你首先要预知有好多个特定类型要使用上面通用结构类,然后要根据这些不同的类型写不同的class

    2.代码冗余,复用率很低,这是显而易见的

    3.一个数据结构的变更要将所有类型的数据结构做相应的修改。

         因为要修改所有基于这个数据结构来实现的特定类型的数据结构

    4.为了提供不可预知的数据类型的支持,还是要提供object类型接口,这样老问题又会出现

    针对上面出现的问题,似乎在原来是没办法解决的问题,但在2.0以后微软推出泛型这一数据结构,可以很好的解决上面的问题。

什么是泛型?

    通过泛型可以定义类型安全类,而不损害类型安全、性能或工作效率,我们可以将下面的结构类

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public class Stack
{
    
object[] m_Items;
    
public Stack()
    { }
    
public void Push(object item)
    { }
    
public object Pop()
    { }
}

修改为

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public class Stack<T>
{
    T[] m_Items;
    
public Stack()
    { }
    
public void Push(T item)
    { }
    
public T Pop()
    { }
}

运行时实例化

1.可以使用任何类型声明和实例化

2.声明和实例化是都必须使用一个特定类型来代替一般类型

3.泛型编程模型的优点是:内部算法和操作保持不变,而实际类型可以在使用时指定

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public partial class _Default : System.Web.UI.Page 
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Stack
<int> stack = new Stack<int>();
        stack.Push(
1);
        stack.Push(
2);

      
int number = stack.Pop();
    }
}

泛型是如何实现的?

    1.在.Net2.0中,泛型在IL和CLR本身中具有本机支持

    2.编译类型时,就像编译其他类型一样,泛型仅保留一个占位符

    3.而用特定类型实例化泛型代码,编译时会将泛型替代为特定的实际类型

泛型的好处

    1.一次性的开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它

    2.编译器支持和类型安全

    3.不会强行对值类型进行装箱和拆箱,或者对引用类型进行向下强制类型转换,所以性能得到显著提高。对于值类型,一般提高200%,对于引用类型,也可提高100%(当然整个应用程序的运行效率也可能会提高,也可能不会提高)。

在结构体中使用泛型

    定义通用结构体

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
public struct Point<T>
{
    
public T x;
    
public T y;
}

    运行实例

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
 Point<int> point;
 point.x 
= 1;
 point.y 
= 2;
C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
Point<double> point;
point.x 
= 1.2;
point.y 
= 2.3;

default方法

    在针对上面的泛型通用结构类中,如果你不希望在堆栈为空时引发异常,而是希望返回堆栈中存储类型的默认值,就要使用到default方法。此方法主要用于将获取泛型编程模型中的泛型类型参数的默认值

    -default(ValueType)=0;

    -default(ReferenceType)=null;

    举例说明:如上面Stack<T>,用default(T)获取默认值。

多个泛型

    单个类型可以指定多个泛型

    比如Custom<K,T>,Custom为类型,K为此类型的第一个泛型,T为第二个泛型,若更多的话用,隔开。

泛型别名

    在文件头部使用using关键字为特定类型取别名

    别名作用范围是整个文件,具体实现

C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 C 泛型编程之概述、default()方法、别名指定与泛型约束 - 木乐 - 木乐的博客 Code
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Stack = Stack<int>;

public partial class _Default : System.Web.UI.Page 
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Stack stack 
= new Stack();
        stack.Push(
1);
        stack.Push(
2);

        
int number = stack.Pop();
    }
}

泛型约束-派生约束

    为什么要使用泛型约束,先看下面方法

    public class LinkedList

    {

        T Find(K key)

        {

                Node<K,T> current = m_Head;

                while(current.NextNode!=null)

                {

                       //因为编译器不知道K类型的key是否支持"=="运算符,例如在默认情况,结构体就不提供这种实现

                       if(current.Key==key) //Will not Compile

                       {

                            break;

                       }

                       else

                       {

                            current=current.NextNode;

                       }

                }

                return current.Item;

         }

     }

     根据上面注释得知,此方法是不会通过编译的,那么采取什么方法来解决呢,我们就想到了IComparable下面的CompareTo方法,如下:

     public interface IComparable

     {

           int CompareTo(object obj);

     }

     解决方案如下:

     public class LinkedList<K,T> where K:IComparable

    {

        T Find(K key)

        {

                Node<K,T> current = m_Head;

                while(current.NextNode!=null)

                {

                       //因为编译器不知道K类型的key是否支持"=="运算符,例如在默认情况,结构体就不提供这种实现

                       if(current.Key.CompareTo(key)==0) //Will not Compile

                       {

                            break;

                       }

                       else

                       {

                            current=current.NextNode;

                       }

                }

                return current.Item;

         }

     }

     注:1.where关键字,若上类中的K和T都要约束,那么两个where之间用空格隔开

         2.K:IComparable表示K只接受实现了IComparable接口的类型

         3.尽管如此,还是无法避免传入值类型的K所带来的装箱问题,原因是IComparable下的CompareTo方法的参数仍是object类型。所以最终的正确代码如下:

    public class LinkedList<K,T> where K:IComparable<T>

    {

        T Find(K key)

        {

                Node<K,T> current = m_Head;

                while(current.NextNode!=null)

                {

                       //因为编译器不知道K类型的key是否支持"=="运算符,例如在默认情况,结构体就不提供这种实现

                       if(current.Key.CompareTo(key)==0) //Will not Compile

                       {

                            break;

                       }

                       else

                       {

                            current=current.NextNode;

                       }

                }

                return current.Item;

         }

     }

     注意:

     1.在C#2.0中,所有的派生约束必须放在类的实际派生列表之后

     public class LinkedList<K,T> :IEnumerable<T> where K:IComparable<K>

     {......}

     2.通常只需要在需要的级别定义类的约束,即:哪里编译异常哪里约束。

     3.一个泛型参数上约束多个接口(彼此用,分隔)

     public class LinkedList<K,T> where K:IComparable<K>,IConvertible

     {......}

     4.在一个约束中最多只能有一个基类,同时约束的基类不能是密封类或静态类,因为密封类不能被继承,静态类不能被实例化。

     5.不能将System.Delegate或System.Array作为泛型基类

     6.可以同时约束一个基类和一个或多个接口,但是基类必须首先出现在约束列表中

     public class LinkedList<K,T> where K:MyBaseClass,IComparable<K>,IConvertible

     {......}

     7.C#允许你将另一个一般类型指定为约束

     public class LinkeList<K,T> where K:T

     {......}

     8.自定义基类或接口进行泛型约束

     自定义接口

     public interface IMyInterface

     {......}

     public class MyClass<T> where T:IMyInterface

     {......}

     MyClass<IMyInterface> obj=new MyClass<IMyInterface>();

     自定义基类

     public class MyOtherClass

     {......}

     public class MyClass<T> where T:MyOtherClass

     {......}

     MyClass<MyOtherClass> obj=new MyClass<MyOtherClass>();

     9.自定义的基类或接口必须与泛型参数具有一致的可见性

     正确的可见性

     public class MyBaseClass

     {......}

     internal class MySubClass<T> where T:MyBaseClass

     {......}

     错误的可见性

     internal class MyBaseClass

     {......}

     public class MySubClass<T> where T:MyBaseClass

     {......}

泛型约束-构造函数约束

      假设你要在一个一般类的内部实例化一个新的一般对象。问题在于,C#编译器不知道客户将要使用的类型实参是否具有匹配的构造函数,因而它拒绝编译实例化行。

      为了解决该问题,C#运行约束一般类型参数,以使其必须支持公共支持的默认构造函数这是使用new()约束来完成的

      class Node<K,T> where T:new()

      {

           public K key;

           public T item;

           public Node<K,T> NextNode;

           public Node()

           {

                key=default(K);

                item=new T();

                NextNode=null;

           }

      }

      注意:

      可以将构造函数和派生约束混合起来,前提是构造函数约束要放到约束列表的最后

      public class LinkedList<K,T> where K:IComparable<K>,new()

      {......}

泛型约束-值/引用类型约束

      可以使用struct约束将一般类型参数约束为值类型(例如:int,bool和enum),或任何自定义结构

      public class MyClass<T> where T:struct

      {......}

      可以使用class约束将一般类型参数约束为引用类型(类)

      public class MyClass<T> where T:class

      {......}

      注意

      1.不能将引用/值类型约束与基类约束一起使用,因为基类约束涉及到类。

      2.不能使用结构和默认构造函数约束,因为默认构造函数约束也涉及到类。

      3.虽然你可以使用类和默认构造函数约束,但是这样做没有任何意义。

      4.可以将引用/值类型约束与接口约束组合起来,前提是引用/值类型约束必须放在约束列表的开头。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值