基础拾遗------泛型详解

前言:  

泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符。类型参数使得设计类和方法时,不必确定一个或多个具体参数,具体参数可延迟到客户代码中声明、实现。使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。就像我们写一个类MyList<T>,客户代码可以这样调用:MyList<int>, MyList<string>或 MyList<MyClass>。方便我们设计更加通用的类型,也避免了容器操作中的装箱和拆箱操作

PS:为什么要避免装箱和拆箱?  我们知道值类型存放在堆栈上,引用类型存放在堆 上。 (1)装箱:CLR需要做额外的工作把堆栈上的值类型移动到堆上,这个操作就被称为装箱. (2)拆箱:装箱操作的反操作,把堆中的对象复制到堆栈中,并且返回其值。 装箱和拆箱都意味着堆和堆栈空间的一系列操作,毫无疑问,这些操作的性能代价是很大的,尤其对于堆上空间的操作,速度相对于堆栈的操作慢得多,并且可能引发垃圾回收,这些都将大规模地影响系统的性能。

1.泛型的约束

在指定一个类型参数时,可以指定类型参数必须满足的约束条件,约束是使用 where 上下文关键字指定的。

下表列出了五种类型的约束:

约束

说明

T:struct

类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。

T:class

类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

T:new()

类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。

T:<基类名>

类型参数必须是指定的基类或派生自指定的基类。

T:<接口名称>

类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

T:U

为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束.

1.1.派生约束

基本形式 where T:base-class-name

1.1.1.常见的

public class MyClass<T> where T :IComparable { }

1.1.2.约束放在类的实际派生之后

public class B { }
public class MyClass<T> : B where T : IComparable { }

1.1.3.可以继承一个基类和多个接口,且基类在接口前面

基本形式 where T:interface-name

public class B { }
public class MyClass<T> where T : B, IComparable, ICloneable { }

1.2.构造函数约束

基本形式: where T : new()

1.2.1.常见的

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

1.2.2.可以将构造函数约束和派生约束组合起来,前提是构造函数约束出现在约束列表的最后

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

1.3.值约束

基本形式:where T : struct

1.3.1.常见的

public class MyClass<T> where T : struct { }

1.3.2.与接口约束同时使用,在最前面(不能与基类约束,构造函数约束一起使用)

public class MyClass<T> where T : struct, IComparable { }

1.4.引用约束

基本形式:where T : class

1.4.1.常见的

public class MyClass<T> where T : class { }

1.5.多个泛型参数

基本形式:where T : class where u:class

 public class MyClass<T, U> where T : IComparable  where U : class { }

1.6.继承和泛型

public class B<T>{ }

1.6.1. 在从泛型基类派生时,可以提供类型实参,而不是基类泛型参数

public class SubClass : B<int>{ }

1.6.2.如果子类是泛型,而非具体的类型实参,则可以使用子类泛型参数作为泛型基类的指定类型

public class SubClass<R> : B<R>{ }

1.6.3.在子类重复基类的约束(在使用子类泛型参数时,必须在子类级别重复在基类级别规定的任何约束)

public class B<T> where T : ISomeInterface { }
public class SubClass2<T> : B<T> where T : ISomeInterface { }

1.6.4.构造函数约束

   public class B<T> where T : new()
    {
        public T SomeMethod()
        {
            return new T();
        }
    }
    public class SubClass3<T> : B<T> where T : new(){ }

2.泛型继承

1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;

2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;

/如果这样写的话,显然会报找不到类型T,S的错误  
public class TestChild : Test< T, S> { }  
 
//正确的写法应该是  
public class TestChild : Test< string, int>{ }  
public class TestChild< T, S> : Test< T, S> { }  
public class TestChild< T, S> : Test< String, int> { } 

3.关键字default

我们在不知道参数是值类型还是引用类型;是值类型的时候不知道他是结构还是数值的情况下定义了T,如果需要返回泛型类型的默认值则会用到这个关键字。这个时候就要用到default了。

1.T是值类型而非结构的则defaultT) 数值类型返回0,字符串返回空

2.T 是非引用类型是结构时候返回初始化为零或空的每个结构成员

3.引用类型返回NULL

class DefaultTest<T>
    {
        public T Main()
        {
            T t = null; 
            return t;
        }
    }

如上如果类型为int则会异常,用 return default(T),就可以避免这种问题。

4.泛型方法

4.1.泛型方法是使用类型参数声明的方法

 void Method<T>(T t){}

也可以写成

 void Method<T>(t){}

4.2.泛型方法也有相应的约束

(1) public void MyMethod<X>(X x) where X:IComparable<X>
(2) public class MyClass<T> where T:IComparable<T>
    {
     public void MyMethod<X>(X x,T t) where X:IComparable<X> 

注:实例(2)在类上已经有相应T的约束,在方法中就不能在给T加新的约束了。

4.3.泛型虚方法

泛型虚方法在重写的时候,一定要重新定义泛型,并且也不能重复基类的虚方法约束。

如下:

public class BaseClass
    {
        public virtual void Method<T>(T t)
        {
            //
        }
    }
    public class Class :BaseClass
    {
        public override void Method<X>(X x)
        {
            //
        }
    }

5.泛型接口

5.1.泛型接口实

interface IPerson<T>
    {
        void add( T t);
    }
 class PersonManager : IPerson<Person>
    {
        #region IPerson<Person> 成员
        public void add( Person t )
        {
           //
        } 
//一个接口可定义多个类型参数
interface IDictionary<K, V>
{
//
}

5.2.多重接口可作为单个类型上的约束

class Stack<T> where T : System.IComparable<T>, IEnumerable<T>

5.3.泛型接口继承也遵循类之间的规则

interface IMonth<T> { }

interface IJanuary     : IMonth<int> { }  //No error
interface IFebruary<T> : IMonth<int> { }  //No error
interface IMarch<T>    : IMonth<T> { }    //No error
//interface IApril<T>  : IMonth<T, U> {}  //Error

6.泛型数组

下限为零的一维数组自动实现 IList<T>。 这使您可以创建能够使用相同代码循环访问数组和其他集合类型的泛型方法。 此技术主要对读取集合中的数据很有用。 IList<T> 接口不能用于在数组中添加或移除元素。 如果尝试对此上下文中的数组调用 IList<T> 方法(例如 RemoveAt),则将引发异常。

7.泛型委托

ps:委托是什么?

使用委托可以将方法作为参数进行传递。委托是一种特殊类型的对象,其特殊之处在于委托中包含的只是一个活多个方法的地址,而不是数据。定义方式如:public delegate void MethodDelegate();稍后的文章会有详细介绍。

http://www.cnblogs.com/kmonkeywyl/p/5626432.html这篇文章叙述了我在封装此类库的时候泛型委托就起了很大作用。

public delegate IHttpActionResult apiaction();
    public delegate IHttpActionResult apiaction_l(long args);
    public delegate IHttpActionResult apiaction_ll(long args1, long args2);
    public delegate IHttpActionResult apiaction_li(long args1, int arg2);
    public delegate IHttpActionResult apiaction_ls(long args1, string args2);
    public delegate IHttpActionResult apiaction_i(int args1);
    public delegate IHttpActionResult apiaction_ii(int args1, int args2);
    public delegate IHttpActionResult apiaction_is(int args1, string args2);
    public delegate IHttpActionResult apiaction_il(int args1, long args2);
    public delegate IHttpActionResult apiaction_si(string args1, int args2);
    public delegate IHttpActionResult apiaction_ss(string args1, string args2);
    public delegate IHttpActionResult apiaction_sl(string args1, long args2);
    public delegate IHttpActionResult apiaction_sss(string args1, string args2, string args3);
public delegate IHttpActionResult apiaction_o<treq>(treq data) where treq : class,new();

7.1.首先介绍两个特殊的泛型委托Action<T>和Fun<TResult>

Action<T>只能委托必须是无返回值的方法

Fun<TResult>只是委托必须有返回值的方法

针对于Action<T>和Fun<TResult>会在后边委托篇章里介绍。这边只需知道泛型委托有两个特殊的他们即可。

7.2.由于泛型的引入,所以一些内建(Built-in)的类、接口、委托都有了各自的泛型版本。

EventHandler也不例外,它有了自己的泛型版本:EventHandler<T>。

定义如下:

[Serializable]  
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs: EventArgs;

第二个参数的类型由EventArgs变成了TEventArgs,而TEventArgs具体是什么,则由调用方决定。假设IntEventArgs和StringEventArgs都继承于System.EventArgs,那么:

1.EventHandler<IntEventArgs>指代这样一类函数:这些函数没有返回值,有两个参数,第一个参数是object类型,第二个参数是IntEventArgs类型

2.EventHandler<StringEventArgs>指代这样一类函数:这些函数没有返回值,有两个参数,第一个参数是object类型,第二个参数是StringEventArgs类型

其实EventHandler<IntEventArgs>和EventHandler<StringEventArgs>是两个完全不同的委托,它们所指代的函数都分别有着不同的签名形式。请参见下面的示例:

class IntEventArgs : System.EventArgs  
{  
  public int IntValue { get; set; }  
  public IntEventArgs() { }  
  public IntEventArgs(int value)  
  { this.IntValue = value; }  
}  
  
class StringEventArgs : System.EventArgs  
{  
  public string StringValue { get; set; }  
  public StringEventArgs() { }  
  public StringEventArgs(string value)  
  { this.StringValue = value; }  
}  
  
class Program  
{  
  static void PrintInt(object sender, IntEventArgs e)  
  {  
    Console.WriteLine(e.IntValue);  
  }  
  
  static void PrintString(object sender, StringEventArgs e)  
  {  
    Console.WriteLine(e.StringValue);  
  }  
  
  static void Main(string[] args)  
  {  
    EventHandler<IntEventArgs> ihandler = new EventHandler<IntEventArgs>(PrintInt);  
    EventHandler<StringEventArgs> shandler = new EventHandler<StringEventArgs>(PrintString);  
  
    ihandler(null, new IntEventArgs(100));  
    shandler(null, new StringEventArgs("Hello World"));  
  }  
}  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

C#基础知识系列三(类和结构体、String和StringBuilder、equals和==)

   这一节主要来了解一下类和结构体之间的异同点、以及针对String和StringBuilder的用法、equals和==,其实可以看出很多地方都用到了上一节...

844
来自专栏Java与Android技术栈

Scala学习笔记(八)

模式匹配是 Scala 的重要特性之一,前面两篇笔记Scala学习笔记(六) Scala的偏函数和偏应用函数、Scala学习笔记(七) Sealed Class...

1243
来自专栏程序员互动联盟

【专业知识】C++的强制类型转换

C 风格(C-style)强制转换一般用(类型修饰)来转换类型。C++则有自己的转换方式 使用标准C++的类型转换符:static_cast、dynamic_c...

2944
来自专栏hbbliyong

泛型介绍(接上一篇,具体的事例随后呈上)

2.2接口约束 为了规定某个数据类型必须实现某个接口,需要声明一个接口约束(interface constraint).有了这种约束之后,甚至不需要执行类型转换...

3085
来自专栏xingoo, 一个梦想做发明家的程序员

static_cast const_cast reindivter_cast dynamic_cast

C 风格(C-style)强制转型如下: (T) exdivssion // cast exdivssion to be of type T 函数风格(Func...

20910
来自专栏DHUtoBUAA

查找数组中重复的数字

        题目来源于《剑指Offer》中的面试题3:找出数组中重复的数字。   // 题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中...

3926
来自专栏小白鼠

Java8特性接口的改变LambaStream时间API

函数式接口,该接口中只能由一个抽象方法,可以使用@FunctionalInterface注解修饰某个接口有且仅有一个抽象方法。

822
来自专栏水击三千

JavaScript操作符(布尔操作符、乘性操作符和加性操作符)

布尔操作符 布尔操作符用来测试两个值的关系,布尔操作符有三个,逻辑非(!)、逻辑与(&&),逻辑或(||)。 逻辑非由一个叹号(!)组成,可以应用于JavaSc...

1956
来自专栏有趣的Python

2-Java常用工具类-包装类

但家族却存在几个异类,它们不能像对象一样进行属性,方法的调用,以及相互之间的对象化处理(对象交互)

491
来自专栏移动端开发

Swift 内存管理详解

Swift内存管理: Swift 和 OC 用的都是ARC的内存管理机制,它们通过 ARC 可以很好的管理对象的回收,大部分的时候,程序猿无需关心 Swift...

1989

扫码关注云+社区