3-C# 泛型(随笔)

本文深入探讨C#中的泛型,揭示其如何提高代码性能,确保类型安全,并促进二进制代码重用。通过对比ArrayList和List<T>,阐述装箱与拆箱操作的性能损耗,强调泛型在避免这种损耗上的优势。同时,泛型还提供了代码扩展性和命名约定,使得泛型类能够在多种类型间灵活应用,减少了重复代码。文章以链表类为例,展示了泛型的实现过程,强调了泛型在类型安全和代码效率上的改进。
摘要由CSDN通过智能技术生成

1. 泛型概述

  有了泛型,就可以创建独立于被包含类型的类和方法了,我们不必给不同的类型编写功能相同的许多方法或类,值创建一个方法或类即可。另一个减少代码的选项是使用Object类,但是其不是类型安全的。泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性:如果某个类型不支持泛型类,编译器就会出现错误。 下面介绍泛型的优点和缺点,尤其是:
  • 性能
  • 类型安全性
  • 二进制代码重用
  • 代码的扩展
  • 命名约定

1.1 性能

  值类型存储在栈上,引用类型存储在堆上。C#类是引用类型,结构是值类型。.NET很容易把值类型转换为引用类型,所以可以在需要对象(对象是引用类型)的任意地方使用值类型。**从值类型转换为引用类型成为装箱**。如果方法需要把一个对象作为参数,同时传递一个值类型,此时装箱操作会自动进行。另一方面,装箱的值类型可以使用拆箱操作转换为值类型。在拆箱时,需要使用类型强制转换运算符。
System.Collections: ArrayList类

var list = new ArrayList();
list.Add(44); //装箱操作

int i1 = (int)list[0]; //拆箱操作

foreach (int i2 in list)
{
	Console.WriteLine(i2); //拆箱操作
}

装箱和拆箱操作很容易使用,但性能损失比较大,遍历许多项时尤其如此。

System.Collections.Generic: List<T>
List<>不使用对象,而是在使用时定义类型,在下面的例子中,List<T>类的泛型类型定义为int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作
var list  = new List<int>();
list.Add(44); 

int i1 = list[0];

foreach (int i2 in list)
{
	Console.WriteLine(i2);
}

1.2 类型安全

  泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,就可以在这个集合中添加任意类型。例如我们在ArrayList的几何中添加一个整数、一个字符串和一个MyClass类。如果使用foreach迭代,编译器会编译这段代码,但是会出现一个运行异常,因为不是所有的元素都可以强制转换为int类型。   错误应尽早发现。在泛型类List中,泛型类型T定义了允许使用的类型。如果添加了其他类型,则编译器不会编译这段代码。

1.3 二进制代码的重用

  泛型允许更好的重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。不需要像C++那样访问源代码。
var list = new List<int>();
list.Add(44);

var stringList = new List<string>();
stringList.Add("mystring");

var myClassList = new List<myClass>();
myClassList.Add(new MyClass());
泛型类型可以在一种语言中定义,在任何其他.NET语言中使用。   

1.4 代码的扩展

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

1.5 命名约定

  下面是泛型类的命名规则:
  • 泛型类型的名称用字母T作为前缀
  • 如果没有特殊的要求,泛型类型允许用任意类替代,且只使用了一个泛型类型,据可以用字符T作为泛型类型的名称
  • 如果泛型类型有特定的要求(例如,它必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应该给泛型类型使用描述性的名称
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

public delegate TOutput Converter<TInput, TOutput>(TInput from);

public class SortedList<TKey, TValue>();

2. 创建泛型类

  首先介绍一个一般的、非泛型的简化链表类,它可以包含任意类型的对象,之后再把这个类型转换为泛型类。
    public class LinkNodeList
    {
        /*
         * 链表中,一个元素引用下一个元素
         * Value:初始化构造函数
         * Previous:上一个元素的引用
         * Next:下一个元素的引用
        */
        public LinkNodeList(object value)
        {
            this.Value = value;
        }

        public object Value { get; private set; }

        public LinkNodeList Previous { get; internal set; }
        public LinkNodeList Next { get; internal set; }
    }
    public class LinkedList
    {
        /*
         * LinkeList类包含LinkedNodeList类型的两个属性Last,First;
         * First: 链表的头
         * Last: 链表的尾
         * AddLast(): 在链表尾添加一个新元素
         * GetEnumerator(): 通过其实现foreach遍历
         */

        public LinkNodeList First { get; private set; }
        public LinkNodeList Last { get; private set; }

        public LinkNodeList AddLast(object node)
        {
            var newNode = new LinkNodeList(node);
            if (First == null)
            {
                First = newNode;
                Last = First;
            }
            else
            {
                LinkNodeList previous = Last;
                Last.Next = newNode;
                Last = newNode;
                Last.Previous = previous;

            }
            return newNode;
        }

        public IEnumerator GetEnumerator()
        {
            LinkNodeList current = First;
            while (current != null)
            {
                yield return current.Value;
                current = current.Next;
            }
        }
    }
使用LinkedList的示例:
    class Program
    {
        static void Main(string[] args)
        {
            LinkedList testList = new LinkedList();
            testList.AddLast(1);
            testList.AddLast(2);
            testList.AddLast(3);

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

            Console.ReadLine();
        }
    }
  下面创建链表的泛型版本。泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。
public class LinkNodeList<T>
{
	public LinkNodeList(T value)
	{
		this.Value = value;
	}
	
	private T Value {get; private set;}
	private LinkNodeList<T> Next {get; internal set;}
	private LinkNodeList<T> Prev {get; internal set;}
}
   下面的代码将LinkedList类也改为泛型类:
public class LinkedList<T>:IEnumerator<T>
{
	public LinkNodeList<T> First {get; private set;}
	public LinkNodeList<T> Last {get; private set;}
    
    public LinkNodeList<T> AddLast(T node)
    {
    	var newNode = new LinkNodeList<T>(node);
    	if (First == null)
    	{
    		First = newNode;
    		Last = First;
    	}
    	else
    	{
    		LinkNodeList<T> previous = Last;
    		Last.Next = newNode;
    		Last = newNode;
    		Last.Prev = previous;

    	}
		
		public IEnumerator<T> GetEnumerator()
		{
			LinkNedeList<T> current = First;
	
			while (current != null)
			{
				yield return current.Value;
				current = current.Next;
			}
		}
    }
	
}

3. 泛型类的功能

  本节讨论下面四个主题:默认值、约束、继承、静态成员
//泛型文档管理器的示例
  //先创建一个新的控制台项目DocumentManager, 并添加DocumentManager<I>类。AddDocument()方法将一个文档添加到队列中。如果队列不为孔,IsDocumentAvailable只读属性就返回true
using System;
using System.Collections.Generics
{
	namespace Wrox.ProCSharp.Generic
	{
		public class DocumentManager<T>
		{
			private readonly Queue<T> documentQuene = new Queue<T>;
			
			public void AddDocument()
			{
				lock(this)
				{
					documentQueue.Enqueue(doc);
				}
			}
	
			public bool IsDocumentAvailable
			{
				get { return documentQueue.Count > 0; }
			}
		}
	}
}

3.1 默认值

当需要将类型T指定为null时,不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能应用于引用类型。
在CSharp中,可以使用default关键字,将null赋予引用,将0赋予值类型

public T GetDocument()
{
	T doc = default(T);
	lock(this)
	{
		doc = documentQueue.Dequeue();
	}
	return doc;
}
Java泛型是Java 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。泛型是将类型参数化,实现代码的通用性。 一、泛型的基本语法 在声明类、接口、方法时可以使用泛型泛型的声明方式为在类名、接口名、方法名后面加上尖括号<>,括号中可以声明一个或多个类型参数,多个类型参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其中,`GenericClass`是一个泛型类,`GenericInterface`是一个泛型接口,`genericMethod`是一个泛型方法。在这些声明中,`<T>`就是类型参数,可以用任何字母代替。 二、泛型的使用 1. 泛型类的使用 在使用泛型类时,需要在类名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子中,`GenericClass`被声明为一个泛型类,`<String>`指定了具体的类型参数,即`data`字段的类型为`String`,`gc`对象被创建时没有指定类型参数,因为编译器可以根据上下文自动推断出类型参数为`String`。 2. 泛型接口的使用 在使用泛型接口时,也需要在接口名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子中,`GenericInterface`被声明为一个泛型接口,`<String>`指定了具体的类型参数,匿名内部类实现了该接口,并使用`String`作为类型参数。 3. 泛型方法的使用 在使用泛型方法时,需要在方法名前面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子中,`genericMethod`被声明为一个泛型方法,`<T>`指定了类型参数,`T data`表示一个类型为`T`的参数,调用时可以传入任何类型的参数。 三、泛型的通配符 有时候,我们不知道泛型的具体类型,可以使用通配符`?`。通配符可以作为类型参数出现在方法的参数类型或返回类型中,但不能用于声明泛型类或泛型接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子中,`printList`方法的参数类型为`List<?>`,表示可以接受任何类型的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`类型来遍历`List`中的元素。 四、泛型的继承 泛型类和泛型接口可以继承或实现其他泛型类或泛型接口,可以使用子类或实现类的类型参数来替换父类或接口的类型参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子中,`SubGenericClass`继承了`GenericClass`,并使用了相同的类型参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的类型参数`T`。 五、泛型的限定 有时候,我们需要对泛型的类型参数进行限定,使其只能是某个类或接口的子类或实现类。可以使用`extends`关键字来限定类型参数的上限,或使用`super`关键字来限定类型参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子中,`GenericClass`的类型参数`T`被限定为`Number`的子类,`GenericInterface`的类型参数`T`被限定为实现了`Comparable`接口的类。 六、泛型的擦除 在Java中,泛型信息只存在于代码编译阶段,在编译后的字节码中会被擦除。在运行时,无法获取泛型的具体类型。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子中,`list`的类型为`List<String>`,但是在运行时,`getClass`返回的类型为`java.util.ArrayList`,因为泛型信息已经被擦除了。 七、泛型的类型推断 在Java 7中,引入了钻石操作符<>,可以使用它来省略类型参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子中,`ArrayList`的类型参数可以被编译器自动推断为`String`。 八、总结 Java泛型是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用泛型时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和类型推断等问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值