Notas de estudio de C # genéricas

Genérico

Genérico es un concepto importante en C #. En pocas palabras, genérico es escribir una clase que pueda apuntar a diferentes tipos. Es decir, se pueden operar múltiples tipos de datos en el mismo código a través de tipos parametrizados. La programación genérica es un paradigma de programación que utiliza "tipos parametrizados" para abstraer tipos para lograr una reutilización más flexible. C # introdujo genéricos en la versión 2.0.

Uso básico de genéricos

Considere un problema de este tipo: si desea definir una colección de listas con varios elementos List, agréguela. Eliminar, búsqueda y otras funciones, pero con el fin de indicar el tipo de elemento puede ser de diversos tipos (por ejemplo int, string, Point, Personetc.), si no es aplicable genérico, hay dos métodos, uno para cada tipo de una sola escritura, tal como:

class IntList{
    
    void Add(int a){
    
    ...}  bool Remove(int a){
    
    ...}}
class StringList{
    
    void Add(string a){
    
    ...}  bool Remove(string a){
    
    ...}}
class PointList{
    
    void Add(Point a){
    
    ...}  bool Remove(Point a){
    
    ...}}
class PersonList{
    
    void Add(Person a){
    
    ...}  bool Remove(Person a){
    
    ...}}

Obviamente, esto es demasiado problema. Otra forma de escribir es escribir una objectclase de colección para elementos, como:

class List{
    
    void Add(object a){
    
    ...}  bool Remove(object a){
    
    ...}}

La última forma de escritura resuelve el problema que objectpuede dirigirse a cualquier tipo, pero la información del tipo se elimina. Si se agrega un stringobjeto a un conjunto int , el sistema no lo sabe.
La forma de resolver este problema es usar genéricos, es decir, agregar un "parámetro de tipo" a la clase anterior para indicar el tipo del elemento, de modo que pueda apuntar a diferentes tipos y especificar el tipo al mismo tiempo. Al definir tipos, utilice corchetes angulares para indicar los parámetros de tipo:

class List<T>
{
    
    
	void Add(T a){
    
    ...}
	bool Remove(T a){
    
    ...}
}

Aquí T es el parámetro de tipo, que representa cualquier tipo. En el uso real, solo necesita especificar el tipo utilizado, como por ejemplo:

	List<int> list1 = new List<int>();  list1.Add(5);.
	List<string> list2 = new List<string>();  list2.Add("abc");
	List<Point> list3 = new List<Point>();  list3.Add(new Point());

Genéricos personalizados

1. Declaración de clase genérica

Una definición de clase puede especificar un conjunto de parámetros de tipo agregando una lista de nombres de parámetros de tipo encerrados entre paréntesis angulares después del nombre de la clase. Los parámetros de tipo se pueden utilizar para definir miembros de clase en el cuerpo de la declaración de clase. P.ej:

public class Pair<TFirst, TSecond>
{
    
    
	public TFirst First;
	public TSecond Second;
}

Cuando utilice una clase genérica, debe proporcionar argumentos de tipo para cada parámetro de tipo:

	Pair<int, string> pair = new Pair<int, string> (){
    
    }
	int i = pair.First;				// First is int
	string s = pair.Second;			// Second is string

De acuerdo con la costumbre, los parámetros de tipo genérico comienzan con una letra T mayúscula. Si solo hay un parámetro de tipo, solo puede haber una letra T mayúscula.
Ejemplo: GenericStark.cspersonalizar una clase de pila genérica.

using System;
public class MyStark<T>
{
    
    
	private T[] buffer;
	private int index = 0;
	private int size;

	public MyStark(int size = 100) {
    
    
		buffer = new T[size];
		this.size = size;
	}

	public void Push(T data) {
    
    
		if (index >= size) throw new Exception();
		buffer[index++] = data;
	}

	public T Pop() {
    
    
		if (index == 0) throw new Exception();
		return buffer[--index];
	}

	public bool IsEmpty() {
    
    
		return index == 0;
	}
}
class Program
{
    
    
	static void Main(){
    
    
		MyStark<string> stark = new MyStark<string>();
		stark.Push("aaa");
		stark.Push("bbbb");
		stark.Push("ccccc");
		while (!stark.IsEmpty()) {
    
    
			string a = stark.Pop();
			Console.WriteLine(a);
		}
	}
}

2. Estructura genérica, interfaz y método genérico

La definición de estructura e interfaz genéricas es similar a la definición de clase genérica. Para métodos genéricos, coloque el tipo de parámetro después del nombre del método, por ejemplo:

	private static Random rnd = new Random();
	static T RandomOneOf<T>(T a, T b) {
    
    
		if (rnd.Next(2) == 0) return a;
		return b;
	}

Al llamar a un método genérico, puede agregar el tipo de parámetro real, pero en la mayoría de los casos, el compilador puede inferir el tipo. En este caso, los corchetes angulares y el tipo real pueden omitirse. Se pueden realizar las siguientes dos formas de escritura:

	string s1 = RandomOneOf<string>("aaa", "bbb");
	string s2 = RandomOneOf("ccc", "ddd");

Ejemplo: GenericMethod.csmétodo genérico personalizado.

using System;
class GenericMethod
{
    
    
	public static void Shuffle<T>(T[] array) {
    
    
		Random rnd = new Random();
		for (int i = 1; i < array.Length; i++) {
    
    
			Swap<T>(array, i, rnd.Next(0, i));
		}
	}

	static void Swap<T>(T[] array, int indexA, int indexB) {
    
    
		T temp = array[indexA];
		array[indexA] = array[indexB];
		array[indexB] = temp;
	}
}
class Program
{
    
    
	static void Main(string[] args) {
    
    
		// 初始化牌局
		int[] array = new int[54];
		for (int i = 0; i < array.Length; i++) {
    
    
			array[i] = i;
		}

		// 洗牌
		GenericMethod.Shuffle<int>(array);

		// 显示
		foreach (int n in array) {
    
    
			Console.Write(n + " ");
		}
	}
}

3. Restricciones de los parámetros de tipo

Cuando se utilizan genéricos, a veces es necesario hacer que los parámetros de tipo cumplan determinadas condiciones, como un determinado tipo 1 y sus subclases. En este momento, es necesario utilizar wherepalabras clave para restringir los parámetros de tipo. Tal como:

class MyList<T>where T:Person

Indica que el parámetro de tipo es una Personclase (y sus subclases).
Las restricciones comunes tienen las siguientes formas:
Inserte la descripción de la imagen aquí
En la programación real, si desea utilizar new T()este método para crear un objeto en el programa, debe agregar where T : new ()dichas restricciones. Si el programa va a utilizar el tipo T null, es necesario utilizar T : classtales restricciones. Si el tipo es una clase también puede ser tipos de valor, puede ser utilizado default, el operador para el valor por defecto ( null,, 0), falsetales como:

	T a = default(T);

Se puede decir que esto defaultse usa específicamente para resolver el problema de los valores predeterminados genéricos.
En la versión C # 7.0, se puede escribir de manera más simple:

	T a = default;

4. Parámetros de entrada y salida en interfaces genéricas

La definición de una interfaz genérica es similar a la definición de una clase genérica, pero en muchos casos, es necesario considerar la amplia adaptabilidad de la interfaz y usar dos modificadores, outy in. Si el tipo de parámetro se modifica sin, el tipo solo se puede usar como el valor de retorno del método y no se puede usar como el parámetro del método; si se modifica con in, el tipo solo se puede usar como el parámetro del método, y no se puede utilizar como el valor de retorno del método. El outmodificado se llama covariante; el inmodificado se llama contravariante; si no lo es oute inútil in, el parámetro de tipo se llama invariante.
En el siguiente ejemplo,

interface C <out X, in Y, Z>
{
    
    
	X M(Y y);
	Z P{
    
    get; set;}
}

XPara covarianza, Ypara contravarianza y Zpara fijo. La covarianza y la contravarianza son principalmente para resolver el problema de conversión entre subclases y clases padre.

Supongo que te gusta

Origin blog.csdn.net/qq_45349225/article/details/114011462
Recomendado
Clasificación