1、协变和抗变

  • 泛型接口的协变

  如果泛型类型用 out  关键字标注,泛型接口就是协变的。这也意味着返回类型只能是 T。 接口IIndex 与类型T 是协变的,并从一个制度索引器中返回这个类型。

  •   

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

  如果对接口IIndex 使用了读写索引器,就把泛型类型T 传递给方法,并从方法中检索这个类型。这不能通过协变来实现—泛型类型必须定义为不变的。不使用out 和 in 标注,就可以把类型定义为不变的。

  • 泛型接口的抗变

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

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

2、因为可空类型使用得非常频繁,所以 C# 有一种特殊的语法,它用于定义可空类型的变量。定义这类变量时,不适用泛型结构的语法,而是用 “?”运算符。在下面的李子中,变量 x1和x2 都是可空的int 类型的实例:

  •   

    Nullable <int> x1;
    int ?x2; 

  可空类型可以与null 和数字比较,如上所示。这里,x的值与null 比较,如果x 不是 null,它就与小于 0的值比较:

  •  int ? x =GetNullableType();
    if(x==null)
    {
    Console.WriteLine("x id null");
    }
    else if(x < )
    {
    Console.WriteLine("x is smaller than 0");
    }

    知道了 Nullable<T> 是如何定义的之后,下面就使用可空类型。可空类型还可以与算术运算符一起使用。变量 x3 是 x1 和x2 的和。 如果这两个可空变量中任何一个的值是 null ,他们的和就是 null

  •  int ? x1=GetNullableType();
    int ? x2=GetNullableType();
    int ? x3=x1+x2;

    !这里调用的 GetNullableThype() 只是一个占位符,它对于任何方法都返回一个可空的 int 。

3、非可空类型可以转换为可空类型。从非可空类型转换为可空类型时,在不需要强制类型转换的地方可以进行隐式转换。

  •  int y1=;
    int ? x1=y1;

    但从可空类型转换为非可空类型可能会失败。如果可空类型的值是null,并且把null 值赋予非可空类型,就会抛出InvalidOperationException 类型的异常。这就是需要类型强制转换运算符进行显示转换的原因:

  • int ? x1=GetNullableType();
    int y1=(int) x1;

    如果不进行显示类型转换,还可以使用合并运算符从可空类型转换为非可空类型,合并运算符的语法是“??”,为转换定义了一个默认值,以防可空类型的值是null。这里,如果 x1 是 null,y1的值就是 0

  • int ? x1 = GetNullableType();
    int y1 = x1 ?? ; 

4、泛型方法:

  除了定义泛型类之外,还可以定义泛型方法。在泛型犯法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。

  • void Swap<T>(ref T x,ref T y)
    {
    T temp;
    temp =x;
    x=y;
    y= temp;
    }

  把泛型类型赋予方法调用,就可以调用泛型方法:

  • int i=;
    int j=;
    Swap<int>(ref i,ref j);
    //或者 Swap(ref i,ref j);

    泛型方法实例,下面的例子使用泛型方法累加集合中的所有元素。为了说明泛型方法的功能,下面使用包含Name 和Balance 属性的Account 类

  •  public class Account
    {
    public string Name{ get; private set;}
    public decimal Balance{get; private set;} public Account(string name,Decimal balance)
    {
    this.Name=name;
    this.Balance=balance;
    }
    }

    其中应累加余额的所有账户操作都添加到 List<Account> 类型的账户列表中

  •  var accounts=new List<Account>()
    {
    new Account("Christian",),
    new Account("Stephanie",),
    new Account("Angela",),
    new Account("Matthias",)
    };

    累加所有Account对象的传统方式是用foreach 语句遍历所有的Account 对象,如下所示,foreach 语句使用 IEnumerable 接口迭代集合的元素,所以 AccumulateSimple() 方法的参数是 IEnumerable类型。foreach语句处理实现 IEunmerable 接口的每个对象。这样,AccumulateSimple() 方法就可以用于所有实现 IEnumerable<Account> 接口的集合类。在这个方法的实现代码中,直接访问Account 对象的 Balance 属性。

  •  public static class Algorithm
    {
    public static decimal AccumulateSimple(IEnumerable<Account> source)
    {
    decimal sum=;
    foreach(Account a in source)
    {
    sum+=a.Balance;
    }
    return sum;
    }
    }

    调用如下:

  • decimal amount=Algorthm.AccumulateSimple(accounts);

    带约束的泛型方法

    第一个实现代码的问题是,它只能用于Account 对象。使用泛型方法就可以避免这个问题。

    Accumulate() 方法的第二个版本接受实现了 IAccount 接口的任意类型。如前面的泛型类所述,泛型类型可以用where 子句来限制。

    •  public static decimal Accmulate<TAccount>(IEnumerable<TAccount> source) where TAccount : IAccount
      {
      decimal sum=; foreach(TAccount a in source)
      {
      sum+= a.Balance;
      }
      return sum;
      }

      重构Account 类实现 IAccount 接口

    • public class Account : IAccount
      {
      //...

      IAccount 接口定义了只读属性 Balance 和 Name

    •  public interface IAccount
      {
      decimal Balance {get; }
      string Name{get ;}
      }

      新的调用方式:

    • decimal amount =Algorthim.Accmulate<Account>(accounts);
      //或者 decimail amount =Alhorthim.Accmulate(accounts);

    带委托的泛型方法

    泛型类型实现了 IAccount 接口的要求过于严格。下面的示例提示了,如何通过传递一个泛型委托来修改 Accumulate()方法。这个Accumulate() 方法使用两个泛型参数 T1 和 T2。第一个参数T1 用于实现了IEnumerable<T1> 参数的集合,第二个参数使用泛型委托Fun<T1, T2, TResult>。 其中, 第二个和第三个泛型参数都是 T2 类型。需要传递的方法有两个输入参数(T1 和 T2)和一个 T2类型的返回值。

    •  public static T2 Accmulate<T1,T2>(IEnumerable<T1> source, Func<T1,T2,T2> action)
      {
      T2 sum=default(T2);
      foreach(T1 item in source)
      {
      sum=action(item,sum);
      }
      return sum;
      }

      在调用这个方法时,需要指定泛型参数类型,因为编译器不能自动推断出该类型。对于方法的第一个参数,所赋予的accounts集合是IEnumerable<Account> 类型。对于第二个参数,使用一个 Lambda 表达式来定义Account 和 decimal 类型的两个参数,返回一个小数。对于每一项通过Accumulate() 方法调用这个 Lambda 表达式

    • decimal amount=Algorithm.Accumulate<Account,decimal>(accounts,(item,sum)=>sum+=item.Balance);

    泛型方法规范

    泛型方法可以重载,为特定的类型定义规范。这也适用于带泛型参数的方法。Foo()方法定义了两个版本,第一个版本接受一个泛型参数,第二个版本是用于int 参数的专有版本。在编译期间,会使用最佳匹配。如果传递了一个int,就选择带int 参数的方法。对于任何其他参数类型,编译器会选择方法的泛型版本

    •  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);
      }
      }

      Foo()方法现在可以通过任意参数类型来调用。下面的示例代码给该方法传递了一个 int 和一个 string

    •  static void Main()
      {
      var test =new MethodOverloads();
      test.Foo();
      test.Foo("abc");
      }

      运行该程序,可以从输出中看出选择了最佳匹配的方法:

    • Foo(int x)
      Foo<T>(T obj), obj type: String

      需要注意的是,所调用的方法是在编译期间定义的,而不是运行期间。这很容易举例说明:添加一个调用Foo() 方法的Bar() 泛型方法,并传递泛型参数值:

    • public class MethodOverloads
      {
      //... public void Bar<T> (T obj)
      {
      Foo(obj);
      }
      }

      Main()方法现在改为调用传递以个 int 值 的Bar()方法:

    • static void Main()
      {
      vat test=new MethodOverloads();
      test.Bar();

      从控制台的输出可以看出,Bar()方法选择了泛型Foo()方法,而不是用 int参数重载的Foo()方法。原因是编译器是在编译期间选择Bar() 方法调用的Foo() 方法。由于Bar() 方法定义了一个泛型参数,而且泛型Foo()方法匹配这个类型,所以调用了Foo() 方法。在运行期间给Bar() 方法传递一个 int 值不会改变这一点。

    • Foo<T>(T obj), obj type: Int32

【小结】:泛型。通过泛型类可以创建独立于类型的类,泛型方法是独立于类型的方法。接口、结构、和委托也可以用泛型的方式创建。泛型引入了一种新的编程方式。我们介绍了如何实现相应的算法(尤其是操作和谓词)以用于不同的类,而且他们呢都是类型安全的。泛型委托可以去除集合中的算法。

C#高级编程笔记 Day 7, 2016年9月 19日 (泛型)的更多相关文章

  1. Python array,list,dataframe索引切片操作 2016年07月19日——智浪文档

    array,list,dataframe索引切片操作 2016年07月19日——智浪文档 list,一维,二维array,datafrme,loc.iloc.ix的简单探讨 Numpy数组的索引和切片 ...

  2. 2016年12月19日 星期一 --出埃及记 Exodus 21:14

    2016年12月19日 星期一 --出埃及记 Exodus 21:14 But if a man schemes and kills another man deliberately, take hi ...

  3. 2016年11月19日 星期六 --出埃及记 Exodus 20:10

    2016年11月19日 星期六 --出埃及记 Exodus 20:10 but the seventh day is a Sabbath to the LORD your God. On it you ...

  4. 2016年10月19日 星期三 --出埃及记 Exodus 19:3

    2016年10月19日 星期三 --出埃及记 Exodus 19:3 Then Moses went up to God, and the LORD called to him from the mo ...

  5. 2016年11月19日--连接查询,变量、if else、while

    连接查询:通过连接运算符可以实现多个表查询.连接是关系数据库模型的主要特点,也是它区别于其它类型数据库管理系统的一个标志. 常用的两个链接运算符: 1.join   on 2.union     在关 ...

  6. 界面显示这个时间格式的js代码: 2016年1月19日 星期二 乙未(羊)年 腊月初十

    today=new Date();function initArray(){ this.length=initArray.arguments.length for(var i=0;i<this. ...

  7. 2016年5月19日php,mysql配置

    1.php配置 1. 配置disable_functiondisable_functions = eval,assert,popen,passthru,escapeshellarg,escapeshe ...

  8. Android高级编程笔记(四)深入探讨Activity(转)

    在应用程序中至少包含一个用来处理应用程序的主UI功能的主界面屏幕.这个主界面一般由多个Fragment组成,并由一组次要Activity支持.要在屏幕之间切换,就必须要启动一个新的Activity.一 ...

  9. 我的Python成长之路---第八天---Python基础(24)---2016年3月5日(晴)

    多线程编程 什么是多线程,线程是操作系统能够进行运算调度的最小单位.他包含在进程之中,是进程中的实际运作单位.线程是进程中一个单顺序的空值六,一个进程可以并发多个线程,每个线程可以并行处理不同的任务. ...

随机推荐

  1. jdbc java数据库连接 6)类路径读取——JdbcUtil的配置文件

    之前的代码中,以下代码很多时候并不是固定的: private static String url = "jdbc:mysql://localhost:3306/day1029?useUnic ...

  2. 概率dp学习

    预备知识 一.期望的数学定义 如果X 是一个离散的随机变量,输出值为 x1, x2, ..., 和输出值相应的概率为p1, p2, ... (概率和为 1), 那么期望值为E(x)=x1p1+x2p2 ...

  3. 关于SQL SERVER数据库学习总结

    对于SQL SERFVER数据库也学了有一阵子了,自己也对自己所学做了一些总结. 我们首先学习数据库设计的一些知识点和用SQL语句建库. 设计数据库步骤:需求分析阶段,概要设计阶段,详细设计阶段, 建 ...

  4. CXF 动态创建客户端调用稳定版本号为2.7.18

    今天用动态创建客户端的方式调用webservice,报了这样一个错: 2017-01-05 20:51:46,029 DEBUG main org.apache.cxf.common.logging. ...

  5. jQuery初学:find()方法及children方法的区别分析

    首先看看英文解释吧: children方法: find方法: 通过以上的解释,可以总结如下: 1:children及find方法都用是用来获得element的子elements的,两者都不会返回 te ...

  6. 小记:目标数组的长度不够。请检查 destIndex 和长度以及数组的下限。

    异常:System.ArgumentException: 目标数组的长度不够.请检查 destIndex 和长度以及数组的下限.(不好意思忘记截图了) 发生异常的代码如下: var list = ne ...

  7. 那些年构建SSH所遇到的坑

    SSH框架有非常多的优点,在这里我不再赘述,我们经常会去构建这种框架的项目,但是在构建SSH时候经常会遇到一些问题,例如常见的网页上的所报的错误404,500等,404错误一般比较好排查,没有找到该页 ...

  8. redis-windows执行redis-cli查询

    1.无密码时的访问 redis-cli -h redis > redis > keys *1) "myset"2) "mysortset" redi ...

  9. python 中文乱码问题2

    1.文件存为utf-8的格式,windows和linux两个环境都可以用 2.中文输出可以采用u方法 比如:print u'成年'

  10. RabbitMQ Topic exchange

    Topic exchange topic与之前的每个类型都不同(ps:废话每个都是不同的).Topic解决了我们另一个需求.举个例子,有一个做资讯的公司,他们会收集各种科技公司的动态并且第一时间转发出 ...