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
, Person
etc.), 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 object
clase 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 object
puede dirigirse a cualquier tipo, pero la información del tipo se elimina. Si se agrega un string
objeto 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.cs
personalizar 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.cs
mé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 where
palabras 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 Person
clase (y sus subclases).
Las restricciones comunes tienen las siguientes formas:
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 : class
tales 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
), false
tales como:
T a = default(T);
Se puede decir que esto default
se 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, out
y 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 out
modificado se llama covariante; el in
modificado se llama contravariante; si no lo es out
e 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;}
}
X
Para covarianza, Y
para contravarianza y Z
para fijo. La covarianza y la contravarianza son principalmente para resolver el problema de conversión entre subclases y clases padre.