zoukankan      html  css  js  c++  java
  • C#之泛型

      泛型不仅是C#的一部分,而且与程序集中的IL代码紧密地集成。有了泛型,就可以创建独立于被包含类型的类和方法。这样就可以不必给不同的类型编写功能相同的许多方法或类,只创建一个方法或类即可。
      另一个减少代码的选项是使用Object类,因为Object类是不安全的。
      泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性:如果某个类型不支持泛型类,编译器就会出现错误。
      泛型不仅限于类,接口和方法也可以使用泛型。
      泛型并不是一个全新的结构,在C++中模版就和泛型相似。泛型不仅是C#的一种结构,而且是CLR定义的。所以,即使泛型类在C#中定义,也可以VB中用一个特定的类型实例化该泛型。
    一.泛型的特性
      1.性能
      对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。虽然操作很容易,但性能损失比较大:
      var list = new ArrayList();
      list.Add(4);//装箱操作,ArrayList的Add方法参数是Object
      foreach(int i in list)
      {
        Console.WriteLine(i);//拆箱
      }

      System.Collections和System.Collections.Generic是非泛型和泛型集合类。System.Collections.Generic中的List<T>类不使用对象,而是在使用时定义类型。
      var list = new List<int>();
      list.Add(4);
      foreach(int i in list)
      {
        Console.WriteLine(i);
      }

      2.类型安全
      使用ArrayList类在集合中可以添加任意类型。
      var list = new ArrayList();
      list.Add(4);
      list.Add(“66”);

      在遍历时就会出现异常,因为不是所有元素都可以强制装换为INT类型:
      foreach(int i in list)
      {
        Console.WriteLine(i);
      }

      错误应尽早发现。使用泛型类List<T>,泛型类型T定义了允许使用特定的类型。有了List<int>的定义,就只能把整数类型添加到集合中。
      var list = new ArrayList();
      list.Add(4);
      list.Add(66);

      3.二进制代码的重用
      泛型允许更好的重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。例如List<T>类可以用int,string,自定义类来实例化。
      泛型类可以在一种语言中定义,在任何其它.NET语言中使用。

      4.代码的扩展
      因为泛型类的定义放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类。但是,在JIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类。引用类型共享一个本地类的所有相同的实现代码。这是因为引用类型在实例化的泛型类中只需要4个字节的内存地址(32为系统),就可以引用一个引用类型。值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求不同,所以要为每个值类型实例化一个新类。

      5.命名约定
      在程序中使用泛型,在区分泛型类型和非泛型类型时使用命名约定就会有一定帮助。
      泛型类型的命名规则:
        *泛型类型的名称用字母T作为前缀。
        *如果没有特殊的要求,泛型类型允许用任意类代替,且如果只能使用一个泛型类型,就可以用T作为泛型类型的名称。
          Public class Person<T>{}
        *如果泛型类型有特定的要求(比如它必须实现某个接口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称:
          public class SortedList<TKey,TValue>{}

    二.使用类型
      1.先创建一个非泛型的简化链表类。

    public class LinkedListNode
        {
            public LinkedListNode(object value)
            {
                this.Value = value;
            }
    
            public object Value { get; private set; }//value对外密封,只可以读取,设置值通过构造函数
            public LinkedListNode Next { get; internal set; }//记录当前节点的下一节点
            public LinkedListNode Prev { get; internal set; }//记录当前节点的上一节点
        }
    
    public class LinkedList : IEnumerable
        {
            public LinkedListNode First { get; private set; }//记录链表的第一个节点,外部只可以读取,设置值通过内部方法
            public LinkedListNode Last { get; private set; }
    
            public LinkedListNode AddLast(object node)
            {
                //实例化一个LinkedListNode
                var newNode = new LinkedListNode(node);
    
                //判断链表是否含有元素,如果没有就把newNode设置为链表的第一个元素
                if (First == null)
                {
                    First = newNode;
                    Last = newNode;
                }
                else
                {
                    LinkedListNode previous = Last;//获取链表的最后一个元素
                    Last.Next = newNode;//将newNode赋予最后一个元素的下一个元素
                    Last = newNode;//重新设置链表的最后一个元素
                    Last.Prev = previous;//newNode成为链表的最后一个元素,将原来的最后一个元素设置为newNode的上一个元素
    
                    //注意上述顺序
                }
                return newNode;
            }
    
            //实现IEnumerable接口的GetEnumerator()方法,这样外部代码就可以用foreach语句遍历链表
            public IEnumerator GetEnumerator()
            {
                LinkedListNode current = First;
                while (current != null)
                {
                    yield return current.Value;//yield创建一个枚举器的状态机
                    current = current.Next;
                }
            }
        }

    客户端代码:
      var list1 = new LinkedList();
      list1.AddLast(2);
      list1.AddLast(4);
      list1.AddLast("22");

      foreach (object i in list1)
      {
        Console.WriteLine(i.ToString());
      }
    需要进行装箱拆箱操作。

    2.下面编写一个泛型版本

      

    public class LinkedListNode<T>
        {
            public LinkedListNode(T value)
            {
                this.Value = value;
            }
    
            public T Value { get; private set; }//value对外密封,只可以读取,设置值通过构造函数
            public LinkedListNode<T> Next { get; internal set; }//记录当前节点的下一节点
            public LinkedListNode<T> Prev { get; internal set; }//记录当前节点的上一节点
        }
    
    //该类实现IEnumerable<T>接口,IEnumerable<T>继承自IEnumerable接口,所以需要实现GetEnumerator()方法和IEnumerator<T> GetEnumerator()方法
    public class LinkedList<T> : IEnumerable<T>
        {
            public LinkedListNode<T> First { get; private set; }//记录链表的第一个节点,外部只可以读取,设置值通过内部方法
            public LinkedListNode<T> Last { get; private set; }
    
            public LinkedListNode<T> AddLast(T node)
            {
                //实例化一个LinkedListNode
                var newNode = new LinkedListNode<T>(node);
    
                //判断链表是否含有元素,如果没有就把newNode设置为链表的第一个元素
                if (First == null)
                {
                    First = newNode;
                    Last = newNode;
                }
                else
                {
                    LinkedListNode<T> previous = Last;//获取链表的最后一个元素
                    Last.Next = newNode;//将newNode赋予最后一个元素的下一个元素
                    Last = newNode;//重新设置链表的最后一个元素
                    Last.Prev = previous;//newNode成为链表的最后一个元素,将原来的最后一个元素设置为newNode的上一个元素
    
                    //注意上述顺序
                }
                return newNode;
            }
    
            //IEnumerable<T>接口继承IEnumerable
            //实现IEnumerable<T>接口的GetEnumerator()方法,这样外部代码就可以用foreach语句遍历链表
            public IEnumerator<T> GetEnumerator()
            {
                LinkedListNode<T> current = First;
                while (current != null)
                {
                    yield return current.Value;//yield创建一个枚举器的状态机
                    current = current.Next;
                }
            }
    
            //实现IEnumerable接口的GetEnumerator()方法
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }

    客户端代码:
      var list2 = new LinkedList<int>();
      list2.AddLast(2);
      list2.AddLast(4);
      list2.AddLast(44);

      foreach (int i in list2)
      {
        Console.WriteLine(i);
      }

    三.泛型类的功能
      在创建泛型类时,有时需要一些C#关键字。
      通过一个例子来介绍这些关键字:

    public class DocumentManager<TDocument>
          where TDocument : IDocument
      {
        private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();
    
        public void AddDocument(TDocument doc)
        {
          lock (this)
          {
            documentQueue.Enqueue(doc);
          }
        }
    
        public bool IsDocumentAvailable
        {
          get { return documentQueue.Count > 0; }
        }
    
        public void DisplayAllDocuments()
        {
          foreach (TDocument doc in documentQueue)
          {
            Console.WriteLine(doc.Title);
          }
        }
    
    
        public TDocument GetDocument()
        {
          TDocument doc = default(TDocument);
          lock (this)
          {
            doc = documentQueue.Dequeue();
          }
          return doc;
        }
    
      }

      1.默认值
      在GetDocument()方法中,需要把TDocument指定为null。但是,不能把null赋予泛型类型。因为泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default,将null赋予引用类型,将0赋予值类型。

      2.约束
      如果泛型类需要调用泛型类型中的方法,就必须添加约束。

    public interface IDocument
      {
        string Title { get; set; }
        string Content { get; set; }
      }
    
      public class Document : IDocument
      {
        public Document()
        {
        }
    
        public Document(string title, string content)
        {
          this.Title = title;
          this.Content = content;
        }
    
        public string Title { get; set; }
        public string Content { get; set; }
      }

      在方法中遍历显示时,需要使用Document类的属性Title,这就需要约束泛型类型TDocument中包含这个属性,这里使用泛型类型TDocument必须实现IDocument接口。where子句指定了该约束。

      泛型支持的约束类型:
      约束              说明
      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(),MyClass

      3.继承
      泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类。要求是必须重复接口的泛型类型,或者必须指定基类的类型。
      派生类可以是泛型类或非泛型类。

      4.静态成员
      泛型类的静态成员和非泛型类的静态成员有区别,泛型类的静态成员只能在类的一个实例中共享:
      public class StaticDemo<T>
      {
        public static int x;
      }
      客户端代码:
      StaticDemo<string>.x = 4;
      StaticDemo<int>.x = 5;
      //存在两组静态字段。

    四.泛型接口
      使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。
      参考http://www.cnblogs.com/icyJ/archive/2012/11/16/covariant.html
    五.泛型结构
      泛型结构类似于泛型类,只是没有继承特性。

    六.泛型方法
      在泛型方法中,泛型类型用方法来定义。泛型方法可以在非泛型类中定义。
      void Swap<T>(ref T x,ref T y)
      {
        T temp;
        temp = x;
        x=y;
        y=temp;
      }

      int i=4;
      int j = 5;
      Swap<int>(ref i,ref j);
      因为C#编译器会通过调用Swap()方法来获取参数的类型,所以不需要把泛型类型赋予方法调用:
      int i=4;
      int j = 5;
      Swap(ref i,ref j);

      1.带约束的泛型方法
      public static class Algorithm
      {

        public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
        where TAccount : IAccount
        {
          decimal sum = 0;

          foreach (TAccount a in source)
          {
            sum += a.Balance;
          }
          return sum;
        }

      }

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


      public class Account : IAccount
      {
        public string Name { get; private set; }
        public decimal Balance { get; private set; }

        public Account(string name, Decimal balance)
        {
          this.Name = name;
          this.Balance = balance;
        }
      }

      客户端代码:
      var accounts = new List<Account>()
      {
        new Account("Christian", 1500),
        new Account("Stephanie", 2200),
        new Account("Angela", 1800),
        new Account("Matthias", 2400)
      };

      decimal amount = Algorithm.Accumulate<Account>(accounts);

      2.带委托的泛型方法
      public static class Algorithm
      {
        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;
        }

      }

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

      3.泛型方法规范
      泛型方法可以重载,为特定的类型定义规范。这样适用于带参数的方法。

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

      如果传递了一个int,就选择带int参数的方法。对于其它参数类型,编译器会选择泛型方法。
      var test = new MethodOverloads();
      test.Foo(33);
      test.Foo("abc");

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

      所调用的方法是在编译期间定义的,而不是运行期间。
      test.Bar(44);
      输出:Foo<T>(T obj), obj type: Int32
      Bar()方法选择了泛型Foo()方法,而不是int参数重载的方法。因为编译器是在编译期间选择Bar()方法调用的Foo()方法。由于Bar()方法定义了一个泛型参数,而且泛型Foo()方法匹配这个类型,所以调用Foo()方法。

  • 相关阅读:
    滑动窗口模板
    交换机命令
    针对织梦程序列表字段内可有可无的显示方法
    dedecms中常见问题修改方法
    redis系列之------字典
    1.InfluxDB-官方测试数据导入
    MYSQL第二课
    centos6.8下hadoop3.1.1完全分布式安装指南
    Mysql—添加用户并授权
    什么是全文检索
  • 原文地址:https://www.cnblogs.com/afei-24/p/6728746.html
Copyright © 2011-2022 走看看