• 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);
    }
  • 相关阅读:
    7 文件操作
    初识字典1
    软件工程学习进度
    AAAA
    软件工程期末总结
    【操作系统】实验四 主存空间的分配和回收 截止提交时间:2016.6.17
    约教网站开发(一)
    操作系统实验三
    .Scrum团队成立
    数学精灵改进
  • 原文地址:https://www.cnblogs.com/KevinG/p/3525289.html
Copyright © 2020-2023  润新知