C#高编 - 泛型

1.性能:

泛型的一个主要优点是性能,这是泛型比集合优越的原因。System.Collections.Generic名称空间中的List<T>类不使用对象,而是在使用时定义类型。

使用ArrayList类,在存储对象的时候,执行装箱操作,而在读取值的时候,进行拆箱。如:

var list = new ArrayList();
list.Add(44);//boxing-convert a value type to a reference type

int i1 = (int)list[0];//unboxing-convert a reference type to a value type

foreach(int i2 in list)
{
    Console.WriteLine(i2);
}

对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换成值类型时,需要进行装箱和拆箱。

装箱和拆箱操作很容易使用,但性能损失比较大,遍历许多项时尤其如此。

 

2.类型安全:

泛型的另一个特征是类型安全,在泛型类List<T>中,泛型类型T定义了允许使用的类型。而ArrayList可以存储不同的对象,在遍历时容易出错。

 

3.二进制代码的重用:

泛型允许更好地重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。泛型类型可以在一种语言中定义,在任何其它.NET语言中使用。

 

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> { }

 

5.创建泛型类:

泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。

public class LinkedListNode
{
    public LinkedListNode(object value)
    {
        this.Value = value;
    }

    public object Value { get; private set; }

    public LinkedListNode Next { get; internal set; }
    public LinkedListNode Prev { get; internal set; }
}

//创建泛型版本,用一个泛型类型T声明。属性Value的类型是T,而不是object。构造函数也变为可以接受T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedListNode<T>。

public class LinkedListNode<T>
{
    public LinkedListNode(T value)
    {
        this.Value = value;
    }

    public T Value { get; private set; }

    public LinkedListNode<T> Next { get; internal set; }
    public LinkedListNode<T> Prev { get; internal set; }
}
View Code

每个处理对象类型的类都可以有泛型实现方式。另外,如果类使用了层次结构,泛型就非常有助于消除类型强制转换操作。 

 

6.泛型类的功能:

 不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能用于引用类型。可以使用default关键字,通过default关键字,将null赋予引用类型,将0赋予值类型。

public T GetDocument()
{
    T doc = default(T);//将null赋予引用类型,将0赋予值类型
    lock(this)
    {
        doc = documentQueue.Dequeue();
    }
    return doc;
}

 

7.约束:

用法一:约束泛型类型必须实现接口,这样在遍历派生类时可以通过接口声明使用基础属性

public class DocumentManager<TDocument>  where TDocument : IDocument

 泛型支持的约束类型

约束 说明
where T:struct 对于结构约束,类型T必须是值类型
where T:class 类约束指定类型T必须是引用类型
where T:IFoo 指定类型T必须实现接口IFoo
where T:Foo 指定类型T必须派生自基类Foo
where T:new() 这是一个构造函数约束,指定类型T必须有一个默认构造函数
where T1:T2 这个约束也可以指定,类型T1派生自泛型类型T2。该约束也成为裸类型约束

 

 

 

 

 

注:只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束

  • 使用泛型类型还可以合并多个约束。如:where T:IFoo,new()
  • where子句中,不能定义必须由泛型类型实现的运算符。

 

8:继承:

泛型类可以派生自泛型基类:其要求是必须重复接口的泛型类型,或者必须指定基类的类型。

public class Derived<T> : Base<T>
public class Derived<T> : Base<string>

 派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体的类型实现。

public abstract class Calc<T>
{
    public abstract T Add(T x,T y);
}

public class IntCalc:Calc<Int>
{
    public override int Add(int x,int y)
    {
        return x + y;
    }
}

 

9:静态成员:

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

 

10:协变和抗变:(需要深入学习)

在NET4之前,泛型接口是不变的。NET4通过协变和抗变为泛型接口和泛型委托添加了一个重要的拓展。

协变和抗变指对参数和返回值的类型进行转换。

在.NET中,参数类型是协变的。如:方法的传入参数若为基类,则也可以用其派生类传递。

public void Display(shape o){}
Rectangle r = new Rectangle{width = 5,Height = 2.5}
Display(r);

方法的返回类型是抗变的。如:方法返回类型是基类,不能将其赋予其派生类,反之可以。

public Rectangle GetRectangle();
Shape s = GetRectangle();

如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。

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

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

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

 

11.泛型结构:

泛型结构Nullable<T>,由.NET Framework定义。

解决问题:数据库中的数字和编程语言中的数字有显著不同的特征,因为数据库中的数字可以为空,而C#的数字不能为空。这个问题不仅存在数据库,也存在XML数据映射中。

使用"?"可空运算符:用于定义可空类型的变量,下面的例子都是可空int类型的实例:

Nullable<int> x1;
int? x2;

可空类型可以与null和数字比较。可空类型还可以与算术运算符一起使用,当对两个可空变量进行加和,如果其中一个是null,则他们的和就是null。

非可空类型可以转换为可空类型,在不需要强制类型转换的地方可以进行隐式转换,这种转换总是成功的:

int y1 = 4;
int? x1 = y1;

 但从可空转换为非可空类型时需要强制转换运算符进行显式转换,否则当可空类型的值是null,并且把null值赋予非可空类型,会抛出异常。

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

 

使用"??"合并运算符:从可空转换为非可空类型如果不进行显式类型转换,可以使用合并运算符,为转换定义了一个默认值,以防可控类型的值是null.

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

 

12.泛型方法:

泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。

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

 因为编译器会通过调用Swap方法来获取参数类型,所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用:

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

 带约束的泛型方法:where子句不仅可以用于泛型类同时也可用于泛型方法

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

//调用
decimal amount = Algorithm.Accumulate<Account>(accounts);
//或者
decimal amount = Algorithm.Accumulate(accounts);

 

 带委托的泛型方法

public static T2 Accumulate<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;
}

//调用时需要知道泛型参数类型,因为编译器不能自动推断出该类型。
deciaml amount = Algorithm.Accumulate<Account,decimal>(amounts,(item,sum) => sum += item.Balance);

 

泛型方法规范:

泛型方法可以重载,为特定的类型定义规范。这也适用于带泛型参数的方法,在编译期间,会使用最佳匹配。

//如果传递int,就选择带int参数的方法,对于其它参数类型,编译器会选择方法的泛型版本
public void Foo<T>(T obj) {...}
public void Foo(int x) {...}

 

需要注意的是,所调用的方法是在编译期间定义的,而不是在运行期间。举例说明:

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

//调用时,Bar方法选择了泛型的Foo方法,原因是编译器在编译期间选择Bar方法调用的Foo方法,由于Bar方法定义了一个泛型参数,而且泛型Foo方法匹配这个类型,就算在运行期间给Bar方法传递一个int值也不会改变这点。
static void Main()
{
    var test = new MethodOverloads();
    test.Bar(44);
}

 

 

posted @ 2014-01-18 17:13  阿樂  阅读(261)  评论(0编辑  收藏  举报