c# 高级编程 5章103页 【泛型】【泛型类】

283 阅读4分钟

什么时候使用泛型:

每一个处理对象类型,都可以试着使用泛型的实现方式。

泛型类和非泛型的对比:

一个非泛型类的例子

拆装箱

  • 值类型intLinkedList中实际被对象LinkedListNode包裹着
    • 加入时,需要装箱
    • 使用时,需要拆箱

不是类型安全的

  • 虽然编译时没问题
  • 但运行时,foreach将元素转换为int时,"6"就会有exception

image.png

    class Program
    {
        static void Main()
        {
            var list1 = new LinkedList();
            list1.AddLast(2);
            list1.AddLast(4);
            list1.AddLast("6");  //foreach遍历此项时,会有运行时异常

            foreach (int i in list1)
            {
                Console.WriteLine(i);
            }
        }
    }
    public class LinkedListNode
    {
        public LinkedListNode(object value) => Value = value;

        public object Value { get; }

        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)
        {
            var newNode = new LinkedListNode(node);
            if (First == null)
            {
                First = newNode;
                Last = First;
            }
            else
            {
                Last.Next = newNode;
                Last = newNode;
            }
            return newNode;
        }

        public IEnumerator GetEnumerator()
        {
            LinkedListNode current = First;
            while (current != null)
            {
                yield return current.Value;
                current = current.Next;
            }
        }
    }

一个泛型类的例子

对于LinkedList<int>好处很多:

  • 类型安全的,如果不是int, 就会有编译时错误,及早发现
  • intLinkedList<int>中实际被对象LinkedListNode<int>包裹,但这里无需拆装箱
  • 还实现了IEnumerable的泛型版本,IEnumerable<T>
    class Program
    {
        static void Main()
        {
            var list2 = new LinkedList<int>();
            list2.AddLast(1);
            list2.AddLast(3);
            list2.AddLast(5);

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

            var list3 = new LinkedList<string>();
            list3.AddLast("2");
            list3.AddLast("four");
            list3.AddLast("foo");

            foreach (string s in list3)
            {
                Console.WriteLine(s);
            }
        }
    }
    public class LinkedListNode<T>
    {
        public LinkedListNode(T value) =>
            Value = value;

        public T Value { get; }
        public LinkedListNode<T> Next { get; internal set; }
        public LinkedListNode<T> Prev { get; internal set; }
    }
    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)
        {
            var newNode = new LinkedListNode<T>(node);
            if (First == null)
            {
                First = newNode;
                Last = First;
            }
            else
            {
                Last.Next = newNode;
                Last = newNode;
            }
            return newNode;
        }

        public IEnumerator<T> GetEnumerator()
        {
            LinkedListNode<T> current = First;

            while (current != null)
            {
                yield return current.Value;
                current = current.Next;
            }
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

泛型类的约束和default关键字在泛型中的使用

在泛型中,取决于T是值类型还是引用类型,default将为0或null

泛型支持的约束类型:

约束说明
where T : struct为值类型
where T : class为引用类型
where T : IFoo实现了这个接口
where T : Foo派生自这个基类
where T : new()有一个默认构造函数 (只能约束默认构造函数,其他构造函数不行)
where T1 : T2派生自这个泛型类

示例:

    class Program
    {
        static void Main()
        {
            var dm = new DocumentManager<Document>();
            dm.AddDocument(new Document("Title A", "Sample A"));
            dm.AddDocument(new Document("Title B", "Sample B"));

            dm.DisplayAllDocuments();

            if (dm.IsDocumentAvailable)
            {
                Document d = dm.GetDocument();
                WriteLine(d.Content);
            }
        }
    }
    public interface IDocument
    {
        string Title { get; }
        string Content { get; }
    }

    public class Document : IDocument
    {
        public Document(string title, string content)
        {
            Title = title;
            Content = content;
        }

        public string Title { get; }
        public string Content { get; }
    }
    public class DocumentManager<TDocument>
        where TDocument : IDocument
    {
        private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();
        private readonly object lockQueue = new object();

        public void AddDocument(TDocument doc)
        {
            lock (lockQueue)
            {
                documentQueue.Enqueue(doc);
            }
        }

        public bool IsDocumentAvailable => documentQueue.Count > 0;

        public void DisplayAllDocuments()
        {
            foreach (TDocument doc in documentQueue)
            {
                Console.WriteLine(doc.Title);
            }
        }

        public TDocument GetDocument()
        {
            //如果TDocument可能是值类型,也可能是引用类型
            //这里用default,就可以避免直接将值类型的0,或者引用类型的null,直接赋给变量doc
            TDocument doc = default(TDocument);  
            
            lock (lockQueue)
            {
                doc = documentQueue.Dequeue();
            }
            return doc;
        }
    }

加约束where TDocument : IDocument有好处:

  • 如果不加这个约束,那么就需要写如下代码段:
  • 如果希望TDocument没有实现IDocument这个问题,不推迟到运行时才暴露出来,而是在编译时就暴露出来,那么就可以加上这个接口约束
  • 再者,加上这个约束,可以省去使用IDocument的时候,还得不停地对TDocument做强制类型转换
        public void DisplayAllDocuments()
        {
            foreach (TDocument doc in documentQueue)
            {
                //这里需要强转为IDocument
                //但是如果TDocument没有实现IDocument,就会有运行时错误
                Console.WriteLine((IDocument)doc.Title);
            }
        }

泛型相关的继承

可以继承自谁?

  • 泛型接口, e.g. IEnumerable
  • 泛型基类, e.g. Base
  • 泛型基类,但是T已经明确指定, e.g. Base
//继承泛型接口
public class LinkedList<T> : IEnumerable<T>
{
}
//继承泛型基类
public class Base<T> {}

public class Derived<T> : Base<T> 
{
}
//继承泛型基类,基类的T指定为string
public class Base<T> {}

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

谁可以继承泛型类?

  • 泛型类, e.g.
    • Derived<T>继承了Base<T>
    • Derived<T>继承了Base<string>
    • StringQuery<TRequest>继承了Query<TRequest, string>
  • 非泛型类, e.g IntCalc继承了Calc<int>
//非泛型类,继承,泛型类
public abstract class Calc<T>
{
   public abstract T Add(T x, T y);
   public abstract T Sub(T x, T y);
}
public class IntCalc : Calc<int>
{
   public override int Add(int x, int y) => x + y;
   public override int Sub(int x, int y) => x - y;
}
public class Query<TRequest, TResult> { }

public StringQuery<TRequest> : Query<TRequest, string> 
{

}

泛型类中的静态成员

只能在一个实例中共享

public class StaticDemo<T>
{
    public static int x;
}
StaticDemo<string>.x = 4;
StaticDemo<int>.x = 5;
Console.WriteLine(StaticDemo<string>.x)  //输出4