浅学C#(28)——泛型

版权声明:转载请注明出处 https://blog.csdn.net/le_17_4_6/article/details/86665191

泛型

.net泛型与C++模板
对于C++模板,用特定的类型实例化模板时,是需要模板的源代码的
泛型不仅是C#语言的一种结构,而且是CLR定义的
即使泛型类是在C#中定义的,也可以在Visual Basic中用一个特定的类型实例化该泛型

在C++中,编译器可以检测出在哪里使用了模板的某个特定类型,例如,模板B的A类型,然后编译需要的代码,来创建这个类型。而在C#中,所有操作都在运行期间进行

  • 性能
var  list=new ArrayList();
list.Add(44); //装箱
int  i1=(int) list[0]; //拆箱
foreach (int i2 in list)
  Console.WriteLine(i2);  //拆箱

频繁的装拆箱对性能损失较大,遍历许多项时尤其如此

var  list=new List<int>();
list.Add(44); 
int  i1= list[0]; 
foreach (int i2 in list)
  Console.WriteLine(i2);  

List的泛型类型定义为int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作

  • 类型安全
var  list=new ArrayList();
list.Add(44); 
list.Add(“mystring”);
list.Add(new MyClass());

for  (int i in list)
  Console.WriteLine(i);

可以往集合中添加任意类型,在迭代时并不是所有类型都可以转换成int,所以触发运行异常

var  list=new List<int>();
list.Add(44); 
list.Add(“mystring”); //编译错误
list.Add(new MyClass()); //编译错误

以上代码在编译期间就会出现错误

  • 二进制代码的重用
    泛型类可以定义一次,并且可以用许多不同的类型实例化,而不需要像C++模板那样访问源代码
var  list=new List<int>();
list.Add(44); 
var  stringList=new List<string>();
stringList.Add(“mystring”);
var myClassList=new List<MyClass>();
myClassList.Add(new MyClass());

泛型类可以在一种语言中定义,在任何其他.NET语言中使用

  • 代码的扩展
    因为泛型类的定义放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类
    但是在JIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类
    引用类型共享同一个本地类的所有相同的实现代码
    因为引用类型在实例化的泛型类中只需要4个字节的内存地址,就可以引用一个引用类型
    值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类

  • 泛型类型的命名规则
    泛型类型的名称用字母T作为前缀
    如果没有特殊的要求,泛型类型允许用任意类替代,且只使用了一个泛型类型,就可以使用T作为泛型类型的名称

public  class  List<T>  {   }
public  class  LinkedList<T>  {   }

如果泛型类型有特定的要求(例如它必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称

public  delegate  void  EventHandler<TEventArgs>(object sender, TEventArgs e);
public  delegate  TOutput  Converter<TInput,TOutput>(TInput  from);
public  class  SortedList<TKey,TValue>  {}
创建泛型类
泛型类的功能

泛型文档管理器示例。文档管理器用于从队列中读写文档

using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Generics
{
    public class DocumentManager<T>        
    {
        private readonly Queue<T> documentQueue = new Queue<T>();
        public void AddDocument(T doc)
        {
            lock (this)
            {
                documentQueue.Enqueue(doc);
            }
        }
	public bool IsDocumentAvailable
        {
            get { return documentQueue.Count > 0; }
        }

泛型类型使用时需注意
Class  MyGenericClass<T1,T2,T3>
{
     private  T1  innerT1Object;
     public  MyGenericClass(T1  item)
     {  innerT1Object=item;  }
     public   T1  InnerT1Object
     {    
           get
           {   return  innerT1Object; }
      }
}

不能假定类提供了什么类型

Class  MyGenericClass<T1,T2,T3>
{
     private  T1  innerT1Object;
     public  MyGenericClass(T1  item)
     {  innerT1Object=new  T1();  }//编译错误
     public   T1  InnerT1Object
     {    
           get
           {   return  innerT1Object; }
      }
}

我们不知道T1是什么,也就不能使用它的构造函数,它甚至
可能没有构造函数,或者没有可公共访问的默认构造函数

当比较为泛型类型提供的类型值和null时,只能使用运算符==和!=

public  bool  Compare(T1  op1, T1  op2)
{
    if  (op1 != null && op2 != null)
           return  true;
   else
           return  false;
}

其中,如果T1是一个值类型,则总是假定它是非空的。于是,
在上面的代码中,Compare总是返回true。

public  bool  Compare(T1  op1, T1  op2)
{
    if  (op1 == op2)   //编译错误
           return  true;
   else
           return  false;
}

这段代码假定T1支持==运算符

默认值
不能将null赋予泛型类型,原因是泛型类型可以实例化为值类型,而null只能用于引用类型
为了解决这个问题,可以使用default关键字,将null赋予引用类型,0赋予值类型
在泛型中,default关键字根据泛型类型是值类型还是引用类型,default用于将泛型类型初始化为null或0

约束
如果没有使用约束,代码如下所示。

 public void DisplayAllDocuments()
    {
        foreach (T doc in documentQueue)
        {
            Console.WriteLine(((IDocument)doc).Title);
        }
    }

问题是如果类型T没有实现IDocument接口,这个
类型强制转换就会导致一个运行异常

最好给DocumentManager添加一个约束,T必须实现IDocument接口,同时将T改为TDocument

class  MyGenericClass<T>  where  T: constraint
{
     ……
}
class  MyGenericClass<T>  where  T: constraint1,constraint2
{
     ……
}
class  MyGenericClass<T>  where  T1: constraint1  
                                                where  T2: constraint2
{
     ……
}
class  MyGenericClass<T1,T2>: MyBaseClass, IMyInterface
where T1:constraint1  where  T2: constraint2
{
     ……
}

约束必须出现在继承说明符的后面

只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束
使用泛型类型还可以合并多个约束

 public  class  MyClass<T> where T: IFoo, new()
{
     ……
}

如果使用new()作约束,它就必须是为类型指定的
最后一个约束

继承
泛型类型可以实现泛型接口,也可以派生自一个类
泛型类可以派生自泛型基类

 public  class  LinkedList<T> : IEnumerable<out T>
{
     ……
}
 public  class  Base<T>
{
     ……
}
public  class  Derived<T>: Base<T>
{
      ……
}

其要求是必须重复接口的泛型类型,或者必须指定基类的类型

 public  class  Base<T>
{
     ……
}
public  class  Derived<T>: Base<string>
{
      ……
}

派生类可以是泛型类或非泛型类

 public  abstract  class  Calc<T>
{
    public  abstract  T  add(T x, T  y);
    public  abstract  T  sub(T x, T  y);
}
public  class  IntCalc: Calc<int>
{
     public  override  int  Add(int  x, int y)
      {   return  x+y;  }
     public  override  int  Sub(int  x, int  y)
     {  return  x-y;  }
}

如果某个类型所继承的基类型中受到了约束,该类型就不能“解除约束”。也就是说,类型T在所继承的基类型中使用时,必须受到至少与基类型相同的约束

class   SuperFarm<T>: Farm<T>  where T: SuperCow
{

}

因为T在Farm中被约束为Animal,把它约束为SuperCow,就是把T约束为这些值中的一个子集,所以这段代码可以正常运行

class   SuperFarm<T>: Farm<T>  where T: struct
{

}

以上代码不会编译

class   SuperFarm<T>: Farm<T>  where T: class
{

}

编译也失败

静态成员
泛型类的静态成员只能在类的一个实例中共享

     public    class   StaticDemo<T>
    {
        public  static  int  x;
    }

    StaticDemo<string>.x=4;
    StaticDemo<int>.x=5;
    Console.WriteLine(StaticDemo<string>.x); //输出4

泛型运算符

 public static implicit operator List<Animal>(Farm<T> farm)
  {
      List<Animal> result = new List<Animal>();
      foreach (T animal in farm)
      {
          result.Add(animal);
      }
      return result;
  }
 public static Farm<T> operator+(Farm<T> farm1, List<T> farm2)
  {
      Farm<T> result = new Farm<T>();
      foreach (T animal in farm1)
          result.Animals.Add(animal);
      foreach (T animal in farm2)
      {
          if (!result.Animals.Contains(animal))
              result.Animals.Add(animal);
      }
      return result;
  }

  public static Farm<T> operator +(List<T> farm1, Farm<T> farm2)
  {
      return farm2 + farm1;
  }

 Farm<Animal> newFarm = farm + dairyFarm;
//在这行代码中,Farm<Cow>实例dairyFarm隐式转换为List<Animal>,大家可能认为下面的代码也可以做到
/*
 public static Farm<T> operator +(Farm<T> farm1, Farm<T> farm2)
  {
      Farm<T> result = new Farm<T>();
      foreach (T t in farm1)         result.Animals.Add(t);
      foreach (T t in farm2)
          if (!result.Animals.Contains(t))
              result.Animals.Add(t);
      return result;
  }
*/
//但是Farm<Cow>不能转换为Farm<Animal>,所以会失败。如果有下列代码就可以解决该问题
/*
public static implicit operator Farm<Animal>(Farm<T> farm)
  {
      Farm<Animal> result = new Farm<Animal>();
      foreach (T t in farm)
          if (t is Animal)
            result.Animals.Add(t);
      return result;
  }
*/

泛型接口
协变和抗变
协变和抗变指对参数和返回值的类型进行转换
.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape
public void Display(Shape o) { }
现在可以传递派生自Shape基类的任意对象

     Rectangle  r=new Rectangle { Width=5, Height=2.5 };
     Display(r);

方法的返回类型是抗变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来可行。

     public  Rectangle  GetRectangle() {    …… }
     Shape   s=GetRectangle();

在C# 4中,扩展后的语言支持泛型接口和泛型委托的协变和抗变

多态性不适用于接口

Cow  myCow=new Cow(“milkCow1”);
Animal myAnimal=myCow;

IMethaneProducer<Cow> cowMathaneProducer=myCow;
//下列代码不能通过编译
IMethaneAnimal<Animal> animalMathaneProducer=cowMathaneProducer;

泛型接口的协变
如果泛型类型用out关键字标注,泛型接口就是协变的
对于接口定义,协变类型参数只能用作方法的返回值或属性get访问器

 public    interface    IIndex<out T>
{
     T   this [int  index]   {  get;  }
     int   Count   {  get;  }         
}

泛型接口的抗变
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样接口只能把泛型类型T用作其方法的输入,不能用作返回类型

 public    interface    IDisplay<in T>
{
         void  Show (T  item);
}
    

ShapeDisplay类实现IDisplay,并使用Shape对象作为输入参数

 public class ShapeDisplay : IDisplay<Shape>
    {
        public void Show(Shape s)
        {
            Console.WriteLine("{0} Width: {1}, Height: {2}", 
                 s.GetType().Name, s.Width, s.Height);
        }
    }

创建ShapeDisplay类的一个新实例,会返回IDisplay,并把它赋予shapeDisplay变量。
因为IDisplay是抗变的,所以可以把结果赋予IDisplay,其中Rectangle派生自Shape,这次接口的方法只能把泛型类型定义为输入,而Rectangle满足Shape的所有要求

 IDisplay<Shape> shapeDisplay = new ShapeDisplay();
 IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
 rectangleDisplay.Show(rectangles[0]);
泛型结构

与类相似,结构也可以是泛型的,只是没有继承特性
Int32是一个结构,而结构的实现同值类型,所以结构不能为空。这个问题不仅存在于数据库中,也存在于把XML类型映射为.NET类型
一种解决方案是数据库和XML文件中的数字映射为引用类型,因为引用类型可以为空值,但也会在运行期间带来额外的开销
使用Nullable很容易解决这个问题
定义Nullable的一个简化版本
类的对象可以为空,所以对类使用Nullable类型是没有意义的

因为可空类型使用得非常频繁,C#有一种特殊的语法

Nullable<int>  x1;
x1=null;  等价于  x1=new Nullable<int>();
int?  x2;

可空类型可以与null和数字比较

int? x3 = null;
if (x3 == null)
    Console.WriteLine("x is null");
else if (x3 < 0)
    Console.WriteLine("x is smaller than 0");

if (x3.HasValue)
{
    ……
}

这不适用于引用类型,即使引用类型有一个HasValue属性,也不能用这种方法。因为引用类型的变量值为空,就表示不存在这个对象,当然不能通过对象来访问属性,否则会抛出异常

使用Value属性可以查看可空类型的值。但如果HasValue是false,访问Value属性就会抛出异常InvalidOperationException

int?  op1=5;
int?  result=op1*2;
//下面的代码不会被编译:
int?  op1=5;
int   result=op1*2;
//为了使上面的代码正常工作,必须进行显式转换:
int?  op1=5;
int   result=(int)op1*2;
//或者通过Value属性访问值:如果op1为null,会引发异常
int?  op1=5;
int   result=op1.Value*2;

可空类型还可以与算术运算符一起使用

int? y1 = 2;
int? y2 = null;
int? y3 = y1 + y2;

只要两个可空变量中的任一个的值是null,它们的和就是null
非可空类型可以转换为可空类型,在不需要强制类型转换的地方可以进行隐式转换,这种转换总是成功的

int   y1 = 4;
int?  x1=y1;            

从可空类型转换为非可空类型可能会失败,如果可空类型的值是null,并且把null值赋予非可空类型,就会抛出InvalidOperationException异常

int?  x1 = null;
int    y1=(int)x1;            

如果不进行显式类型转换,还可以使用合并运算符从可空类型转换为非可空类型

int?  x1 = null;
int    y1= x1 ?? 0;            

下面两个表达式的结果是相同的:

op1 ?? op2
op1==null? op2:op1
泛型方法
void  Swap<T>(ref  T  x,  ref  T  y)
{
    T  temp;
    temp=x;
    x=y;
    y=temp;
}
int   i=4, j=5;
Swap<int>(ref  i, ref  j);

因为C#编译器会通过调用Swap()方法来获取参数的类型,所以不需要把泛型类型赋予方法调用

int   i=4, j=5;
Swap(ref  i, ref  j);

如果类是泛型的,就必须为泛型方法类型使用不同的标识符

public  class  Defaulter<T>
{
   public  T  GetDefault<T>()
    {
         return  default(T);
     }
}

以上代码不会编译

泛型方法规范
泛型方法可以重载,为特定的类型定义规范

public class MethodOverloads  {
    public void Foo<T>(T obj)   {
   Console.WriteLine("Foo<T>(T obj), obj type: {0}", obj.GetType().Name);
    }
    public void Foo(int x)  {
        Console.WriteLine("Foo(int x)");
    }
    public void Bar<T>(T obj)  {
        Foo(obj);
    }
}

在编译期间,会使用最佳匹配。如果传递了一个int,就选择带int参数的方法;对于其他参数类型,编译器会选择方法的泛型版本
所调用的方法是在编译期间定义的,而不是运行期间
Bar()方法选择了泛型Foo()方法,而不是用int参数重载的Foo()方法,原因是编译器在编译期间选择Bar()方法调用的Foo()方法。由于Bar()方法定义了一个泛型参数,而且泛型Foo()方法匹配这个类型,所以调用了Foo()方法,在运行期间给Bar()传递一个int值不会改变这一点

System.Collections.Generic名称空间

Dictionary<K,V>
这个类型可以定义键/值对的集合
每一项的键都必须是唯一的

Dictionary<string,int>  things= new Dictionary<string,int>();
Things.Add(“Green things”, 29);
Things.Add(“Blue things”, 94);
Things.Add(“Yellow things”, 36);
Things.Add(“Red things”, 58);
Things.Add(“Brown things”,87); 

foreach (string  key  in  things.Keys)
    Console.WriteLine(key);
foreach (int  value  in things.Values)
    Console.WriteLine(value);

foreach (KeyValuePair<string,int> thing  in things)
   Console.WritleLine(“{0}={1}”,thing.Key,thing.Value);

定义泛型委托

public  delegate  T1  MyDelegate<T1,T2>(T2 op1, T2 op2)
where  T1:T2;

猜你喜欢

转载自blog.csdn.net/le_17_4_6/article/details/86665191