第6章 泛型

1. 概述

  • 泛型可以创建独立于被包含类型的类和方法。
  • 泛型不仅限于类,还可用于接口和方法。
  • 泛型优点:
    • 性能:List<T>类使用时定义类型,故不再进行装箱和拆箱操作,即性能高。
    • 类型安全:使用时定义了类型,因此可以通过编译检测出不符合的类型。
    • 二进制代码重用:定义一次,但可以用许多不同的类型实例化。
    • 代码的扩展
    • 命名约定:
      • 泛型类型的名称用字母T作为前缀
      • 没有特殊要求,且只使用一个泛型类型,就可以用字符T作为泛型类型的名称
      • 如有特殊要求(如实现接口或派生自基类),或使用两个或以上泛型类型,就使用描述性的名称

                               public delegate void EventHandler<TEventArgs>(objcet sender, TEventArgs e);

                               public delegate TOutput Converter<TInput, TOutput>(TInput from);

                               public class SortedList<TKey, TValue> {}

2. 泛型类的功能

(1) 默认值:default关键字

  • 不能把null赋予泛型类型 => 原因是泛型类型既可以实例化为引用类型,又可以实例化为值类型。而null只能用于引用类型。
  • default关键字,将null赋予引用类型,将0赋予值类型。
public T GetDocument()
{
    T doc = defalut(T);
    lock(this)
    {
          doc = documentQueue.Dequeue();
    }
    return doc;
}    

 (2) 约束:

  • 泛型类需要调用泛型类型中的方法,必须添加约束。
  • 泛型支持的约束类型

  • 可以合并多个约束,如where T : IFoo,new() 即类型T必须实现IFoo接口,且必须有一个默认构造函数
  • 给泛型类型添加约束时,最好包含泛型参数名称的一些信息
// TDocument 来代替 T
// 对于编译器而言,参数名不重要,但更具有可读性
// 即TDocument类型必须实现IDocument接口
public class DocumentManager<TDocument>
      where TDocument : IDocument
{
     //...
}

 (3) 继承

  • 泛型类型可以实现泛型接口(IEnumerable<T>),也可以派生自一个类
  • 派生类可以是泛型类或非泛型类
public class Base<T>
{
}

// 派生自泛型基类
public class Derived<T> : Base<T>
{
}
// 派生自指定基类的类型
public class Derived<T> : Base<string>
{
}

 

  • 还可以创建一个部分的特殊操作
public class Query<TRequest, TResult>
{
}
public StringQuery<TRequest> : Query<TRequest, TResult>
{
}

 

(4) 静态成员:泛型类的静态成员只能在类的一个实例中共享。

// StaticDemo<T>类包含静态字段X
public classs StaticDemo<T>
{
    public static int x;
}
// 同时对不同类型使用泛型类
StaticDemo<string>.x = 4;
StaticDemo<int>.x =5;
WriteLine(StaticDemo<string>.x); // writes 4

 3. 泛型接口

(1) 协变与抗变:指对参数和返回值的类型进行转换

  • 协变:参数类型的转换。可以理解成:父类 -> 子类。父类的对象用子类替换,也可以理解成子类当父类用。例如,函数Act的输入参数为object类型,实际操作中我们可以将string类型的对象传给函数。
private void button1_Click(object sender, EventArgs e)
{
    string str = "这是一个string类型的实例, 函数Act的参数为object, 这里有协变的应用";
    Act(str);
}

void Act(object obj)
{
    return ;
}
  •  抗变:方法的返回类型的转换。可以理解成:子类 -> 父类。子类的对象用父类替换,也可以理解成父类当子类用。抗变也常常翻译为逆变。例如,函数Func的返回类型为string,我们可以将返回的值赋给object对象。
private void button2_Click(object sender, EventArgs e)
{
    //注意这里:Func的返回类型为string, obj的类型为object, string类型继承自object
    object obj = Func();
}

string Func()
{
    return "这里有抗变的应用";
}

 

(2) 泛型接口的协变:用out关键字标注,泛型接口就是协变的。即在接口实现代码里面,T只能用作返回类型,不能用作参数类型。

(3) 泛型接口的抗变:用in关键字标注,泛型接口就是抗变的。即在接口实现代码里面,T用作方法的输入。

//泛型接口支持协变、逆变和不支持协变、逆变的对比
//1-定义一个接口IFoo,既不支持协变,也不支持逆变。
    interface IFoo<T>
    {
        void Method1(T param);
        T Method2();
    }
    //实现接口IFoo
    public class FooClass<T> : IFoo<T>
    {
        public void Method1(T param)
        {
            Console.WriteLine(default(T));
        }
        public T Method2()
        {
            return default(T);
        }
    }

//2-定义一个接口IBar支持对参数T的协变
    interface IBar<out T>
    {
        T Method();
    }
    //实现接口IBar
    public class BarClass<T> : IBar<T>
    {
        public T Method()
        {
            return default(T);
        }
    }

//3-定义一个接口IBaz支持对参数T的逆变
    interface IBaz<in T>
    {
        void Method(T param);
    }
    //实现接口IBaz
    public class BazClass<T> : IBaz<T>
    {
        public void Method(T param)
        {
            Console.WriteLine(param.ToString());
        }
    }
//---------------应用---------------
//1-定义两个有继承关系的类型,IParent和SubClass
    interface IParent
    {
        void DoSomething();
    }
    public class SubClass : IParent
    {
        public void DoSomething()
        {
            Console.WriteLine("SubMethod");
        }
    }

//2-按照协变的逻辑,分别来使用IFoo和IBar
    //IFoo 不支持对参数T的协变
    IFoo<SubClass> foo_sub = new FooClass<SubClass>();
    IFoo<IParent> foo_parent = foo_sub;//编译错误

    //IBar 支持对参数T的协变
    IBar<SubClass> bar_sub = new BarClass<SubClass>();
    IBar<IParent> bar_parent = bar_sub;

//3-按照逆变的逻辑,分别来使用IFoo和IBaz。
    //IFoo 对参数T逆变不相容
    IFoo<IParent> foo_parent = null;
    IFoo<SubClass> foo_sub = foo_parent;//编译错误

    //IBaz 对参数T逆变相容
    IBaz<IParent> baz_parent = null;
    IBaz<SubClass> baz_sub = baz_parent;
泛型接口支持协变、逆变和不支持协变、逆变的对比

部分说明转自:https://www.cnblogs.com/icyJ/archive/2012/11/16/covariant.html

4. 泛型结构:非常类型于泛型类,知识没有继承特性。

  • .NET Framework的一个泛型结构是Nullable<T>
    • 主要用于将数据库中、XML数据中的可以为空的数字与.NET数字(不能为空)类型进行映射
    • 结构Nullable<T>定义了一个约束:T必须是一个结构
    • 定义了只读属性HasValue和Value,以及一些运算符重载。
    • Nullable<T> ==> T 的运算符重载是显示定义
    • T ==> Nullable<T> 的运算符重载是隐式的
Nullable<int> x;
x = 4;
x += 3;
if (x.HasValue)
{
    int y = x.Value;
}
x = null;
  • 使用“?”运算符,定义可控类型的变量(int? x;)
    • 可空类型可以与null比较
    • 可空类型可以与算术运算符一起使用。若两个可空变量运算,任何一个值为null,则运算结果为null。
    • 可空类型转换为非可空类型需进行显式转换,且使用合并运算符(??)定义一个默认值,与空值映射。
int? x1 = GetNullableType(); //GetNullableType()方法只是一个占位符,返回一个可空的int
int y1 = x1 ?? 0;            //x1=null时赋值是默认值0

 5. 泛型方法

  • 在泛型方法中,泛型类型用方法声明来定义
  • 泛型方法可以在非泛型类中定义
  • 所调用的泛型方法是在编译期间而非运行期间定义的,因此泛型方法可以像非泛型方法那样调用
posted @ 2018-09-27 17:01  JJ聆听  阅读(147)  评论(0编辑  收藏  举报