Partilhar via


Genéricos em tempo de execução (guia de programação em C#)

Quando um tipo ou método genérico é compilado em linguagem intermediária comum (CIL), ele contém metadados que o identificam como tendo parâmetros de tipo. A forma como a CIL para um tipo genérico é utilizada difere consoante o parâmetro de tipo fornecido seja um tipo de valor ou um tipo de referência.

Quando um tipo genérico é construído pela primeira vez com um tipo de valor como parâmetro, o tempo de execução cria um tipo genérico especializado com o parâmetro fornecido ou parâmetros substituídos nos locais apropriados na CIL. Tipos genéricos especializados são criados uma vez para cada tipo de valor exclusivo que é usado como parâmetro.

Por exemplo, suponha que o código do programa declarou uma pilha construída de inteiros:

Stack<int>? stack;

Neste ponto, o tempo de execução gera uma versão especializada da Stack<T> classe que tem o inteiro substituído adequadamente para seu parâmetro. Agora, sempre que o código do programa usa uma pilha de inteiros, o tempo de execução reutiliza a classe especializada Stack<T> gerada. No exemplo a seguir, duas instâncias de uma pilha de inteiros são criadas e compartilham uma única instância do Stack<int> código:

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

No entanto, suponha que outra Stack<T> classe com um tipo de valor diferente, como uma long ou uma estrutura definida pelo usuário como seu parâmetro, seja criada em outro ponto do código. Como resultado, o tempo de execução gera outra versão do tipo genérico e substitui a long nos locais apropriados na CIL. As conversões não são mais necessárias porque cada classe genérica especializada contém nativamente o tipo de valor.

Os genéricos funcionam de forma um pouco diferente para os tipos de referência. Na primeira vez que um tipo genérico é construído com qualquer tipo de referência, o tempo de execução cria um tipo genérico especializado com referências de objeto substituídas pelos parâmetros na CIL. Em seguida, sempre que um tipo construído é instanciado com um tipo de referência como parâmetro, independentemente do tipo que é, o tempo de execução reutiliza a versão especializada criada anteriormente do tipo genérico. Isso é possível porque todas as referências são do mesmo tamanho.

Por exemplo, suponha que você tenha dois tipos de referência, uma Customer classe e uma Order classe, e também suponha que você criou uma pilha de Customer tipos:

class Customer { }
class Order { }
Stack<Customer> customers;

Neste ponto, o tempo de execução gera uma versão especializada da classe que armazena Stack<T> referências de objeto que serão preenchidas posteriormente, em vez de armazenar dados. Suponha que a próxima linha de código crie uma pilha de outro tipo de referência, que é chamada Order:

Stack<Order> orders = new Stack<Order>();

Ao contrário dos tipos de valor, outra versão especializada da Stack<T> classe não é criada para o Order tipo. Em vez disso, uma instância da versão especializada da Stack<T> classe é criada e a orders variável é definida para fazer referência a ela. Suponha que você encontrou uma linha de código para criar uma pilha de um Customer tipo:

customers = new Stack<Customer>();

Como no uso anterior da Stack<T> classe criada usando o Order tipo, outra instância da classe especializada Stack<T> é criada. Os ponteiros contidos nele são definidos para fazer referência a uma área de memória do tamanho de um Customer tipo. Como o número de tipos de referência pode variar muito de programa para programa, a implementação C# de genéricos reduz muito a quantidade de código, reduzindo para um o número de classes especializadas criadas pelo compilador para classes genéricas de tipos de referência.

Além disso, quando uma classe C# genérica é instanciada usando um tipo de valor ou parâmetro de tipo de referência, a reflexão pode consultá-la em tempo de execução e tanto seu tipo real quanto seu parâmetro type podem ser determinados.

Consulte também