C# 精通之路 —— 泛型杂谈(性能测试、继承之链表实现、逆变和协变、约束法则),这些细节知识你知多少?

在这里插入图片描述
作者:浪子花梦,一个有趣的程序员 ~
此文章将讲述泛型中一些核心的知识,不会像前面那些文章非常详细,所以就提炼出核心的内容来写吧 . . . C# 语言方面知识相关的文章,以后经常讲述代码,文字尽量少一点,不然我自己都不想看了,哈哈哈 ^ _ ^ . . .


文章目录


泛型的性能测试(List 与 ArrayList 的比较)

为了我们能理解泛型的性能优势,我们通过下面这个程序来比较 List算法 和 FCL(Framework Class Library) 算法的性能,我们分别测试值类型与引用类型作为对象的性能区别 . . .

首先,我们创建一个类,能来测试算法的时间,与垃圾回收的次数,代码如下所示:

internal sealed class OperationTimer : IDisposable
{
	private Stopwatch m_stopwatch;      // 用于测量时间
	private String m_text;
	private Int32 m_collectionCount;

	public OperationTimer(String text)
	{
		PrepareForOperation();

		m_text = text;
		m_collectionCount = GC.CollectionCount(0);      // 获取垃圾回收的次数

		m_stopwatch = Stopwatch.StartNew();             // 测量运行时间
	}

	private static void PrepareForOperation()
	{
		GC.Collect();                                   // 垃圾回收
		GC.WaitForPendingFinalizers();                  // 挂起线程
		GC.Collect();
	}

	public void Dispose()
	{
		// 输入相关的信息:运行时间、垃圾回收次数、类型名称
		Console.WriteLine("{0} (GCs = {1,3}){2}", (m_stopwatch.Elapsed),
				GC.CollectionCount(0) - m_collectionCount, m_text);
	}
}

我们使用 using 开辟上面的对象,将测试代码放在 using的方法体中进行测试 . . .

测试值类型的方法如下所示:

private static void ValueTypePerfTest()
{
	const Int32 count = 100000000;

	using (new OperationTimer("List<Int32>"))
	{
		List<Int32> l = new List<int>();
		for (Int32 n = 0; n < count; ++n)
		{
			l.Add(n);               // 不装箱
			Int32 x = l[n];         // 不拆箱
		}

		l = null;
	}

	using (new OperationTimer("ArrayList of Int32"))
	{
		ArrayList a = new ArrayList();
		for (Int32 n = 0; n < count; ++n)
		{
			a.Add(n);                   // 装箱
			Int32 x = (Int32)a[n];      // 拆箱
		}

		a = null;
	}
}

测试引用类型的方法如下所示:

private static void ReferenceTypePerfText()
{
	const Int32 count = 100000000;

	using (new OperationTimer("List<String>"))
	{
		List<String> l = new List<String>();
		for (Int32 n = 0; n < count; ++n)
		{
			l.Add("X");         // 复制引用
			String x = l[n];    // 复制引用
		}

		l = null;               // 确保进行垃圾回收
	}

	using (new OperationTimer("ArrayList of String"))
	{
		ArrayList a = new ArrayList();
		for (Int32 n = 0; n < count; ++n)
		{
			a.Add("X");                   // 复制引用
			String x = (String)a[n];      // 检查强转
		}

		a = null;
	}
}

我们在 Main中执行上面的两个方法:

public static void Main()
{
	ValueTypePerfTest();
	ReferenceTypePerfText();
}

测试所得的数据如下所示(可能会花费几十秒的时间):
在这里插入图片描述
对应的分别是测试的时间、垃圾回收的次数、类型

我们发现当我们对引用类型进行测试时,区别并不是太大,但是,泛型的优势是不容忽视的 . . .

.


制作一个链表(细节方法的好处)

使用泛型实现链表如下所示:

扫描二维码关注公众号,回复: 11412806 查看本文章
internal sealed class Node<T>
{
    public T m_data;
    public Node<T> m_next;      // 看作是 C++ 中的指针(指向一个东西)
    public Node(T data) : this(data, null) { }
    public Node(T data, Node<T> next)
    {
        m_data = data;
        m_next = next;
    }

    public override string ToString()
    {
        // 重写 ToString   递归
        return this.m_data + (this.m_next != null ? this.m_next.ToString() : String.Empty);
    }
}

测试代码如下所示:

public static void Main()
{
    Node<Char> head = new Node<Char>('C');
    head = new Node<char>('B', head);
    head = new Node<char>('A', head);

    Console.WriteLine(head.ToString());
}

结果如下所示:
在这里插入图片描述

我们已经实现了一个链表,但是这种链表一但类型确定下来之后,就无法改变了,如下所示:
在这里插入图片描述

所以,我们有更好的方法来写出一个链表来,我们可以定义一个基类(非泛型),然后再定义一个派生类(泛型),把派生类中的数据用基类保存下来,这样我们就不需要一种具体的数据类型了,而且这种方法类型安全性高,并防止了类型装箱。代码实现如下所示:

internal class Node
{
    protected Node m_next;
    public Node(Node next) { m_next = next; }
}

internal sealed class TypedNode<T> : Node
{
    public T m_data;
    public TypedNode(T data) : this(data, null) { }
    public TypedNode(T data, Node next) : base(next) { m_data = data; }

    public override string ToString()
    {
        return m_data + (this.m_next != null ? this.m_next.ToString() : String.Empty);
    }
}

测试如下所示:

public static void Main()
{
    Node head = new TypedNode<Int32>(20);
    head = new TypedNode<Char>('A', head);
    head = new TypedNode<String>(" langzi ", head);
    head = new TypedNode<Boolean>(true, head);

    Console.WriteLine(head.ToString());
}

结果如下所示:
在这里插入图片描述
这样,我们就可以指定各种各样的类型了,这样的技巧你们学会了吗?
.


委托与接口的逆变和协变原理

1)逆变量:泛型类型参数可以从一个类更改为它的某个派生类,用 in 关键字标记\

2)协变量:泛型类型参数可以从一个类更改为它的某个基类, 用 out 关键字标记

例如,假定存在以下委托类型定义:

public delegate TResult Func<T, TResult>(T arg); 

现在我们没有用 in out 来修饰它的泛型参数,测试如下所示:
在这里插入图片描述
现在我们来用下面的方面来定义这个委托类型:

public delegate TResult Func<in T, out TResult>(T arg); 

只有这样,上面的代码才能编译通过 . . .

上面我们测试的是委托类型,那么接口是怎么样的呢?其中接口也是一样处理的,如下所示:

interface ITest<in T> { }

像上面一样,直接可以这样赋值:

ITest<Object> test1 = null;

ITest<String> test2 = test1;

.


约束法则演示

我们有的时候会定义一些方法,但这些方法的类型不适用于所有的类型,所以我们需要对外界传过来的类型进行一个约束,如下所示,我们定义一个学生类,其中有学生的年龄,我们定义一个泛型方法,用来比较学生的年龄,通过 where关键字对 学生类进行约束,使得其它的类型无法使用这个方法,如下所示:
在这里插入图片描述
输出的结果为:20 . . .

使用其它的类型测试时,效果如下所示:
在这里插入图片描述

.
.
.


猜你喜欢

转载自blog.csdn.net/weixin_42100963/article/details/107379971