什么时候使用泛型:
每一个处理对象类型的类,都可以试着使用泛型的实现方式。
泛型类和非泛型的对比:
一个非泛型类的例子
拆装箱
- 值类型
int在LinkedList中实际被对象LinkedListNode包裹着- 加入时,需要装箱
- 使用时,需要拆箱
不是类型安全的
- 虽然编译时没问题
- 但运行时,foreach将元素转换为int时,"6"就会有exception
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, 就会有编译时错误,及早发现 int在LinkedList<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