Reificação significa geralmente (fora da ciência da computação) "tornar algo real".
Na programação, algo é reificado se pudermos acessar informações sobre ele na própria linguagem.
Para dois exemplos completamente não genéricos de algo que o C # faz e não tem reificado, vamos usar métodos e acesso à memória.
As linguagens OO geralmente têm métodos (e muitas que não têm funções semelhantes, embora não estejam ligadas a uma classe). Como tal, você pode definir um método em um idioma assim, chamá-lo, talvez substituí-lo e assim por diante. Nem todas essas linguagens permitem que você lide com o próprio método como dados para um programa. C # (e, na verdade, .NET em vez de C #) permite fazer uso de MethodInfo
objetos que representam os métodos; portanto, em métodos de C # são reificados. Métodos em C # são "objetos de primeira classe".
Todas as linguagens práticas têm alguns meios para acessar a memória de um computador. Em uma linguagem de baixo nível como C, podemos lidar diretamente com o mapeamento entre os endereços numéricos usados pelo computador, de modo que gostos int* ptr = (int*) 0xA000000; *ptr = 42;
sejam razoáveis (desde que tenhamos um bom motivo para suspeitar que o acesso ao endereço de memória 0xA000000
dessa maneira tenha vencido ' explodir algo). No C #, isso não é razoável (podemos forçá-lo no .NET, mas com o gerenciamento de memória do .NET movendo as coisas, é pouco provável que seja útil). C # não possui endereços de memória reificados.
Portanto, como refied significa "tornado real" um "tipo reificado" é um tipo sobre o qual podemos "falar" no idioma em questão.
Em genéricos, isso significa duas coisas.
Uma delas é que List<string>
é um tipo tão string
ou int
são. Podemos comparar esse tipo, obter seu nome e perguntar sobre ele:
Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True
Uma conseqüência disso é que podemos "falar sobre" os tipos de parâmetros de um método genérico (ou método de uma classe genérica) dentro do próprio método:
public static void DescribeType<T>(T element)
{
Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
DescribeType(42); // System.Int32
DescribeType(42L); // System.Int64
DescribeType(DateTime.UtcNow); // System.DateTime
}
Como regra, fazer isso demais é "fedido", mas tem muitos casos úteis. Por exemplo, veja:
public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
Comparer<TSource> comparer = Comparer<TSource>.Default;
TSource value = default(TSource);
if (value == null)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
do
{
if (!e.MoveNext()) return value;
value = e.Current;
} while (value == null);
while (e.MoveNext())
{
TSource x = e.Current;
if (x != null && comparer.Compare(x, value) < 0) value = x;
}
}
}
else
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext()) throw Error.NoElements();
value = e.Current;
while (e.MoveNext())
{
TSource x = e.Current;
if (comparer.Compare(x, value) < 0) value = x;
}
}
}
return value;
}
Isso não faz muitas comparações entre o tipo de TSource
e vários tipos para comportamentos diferentes (geralmente um sinal de que você não deveria ter usado genéricos), mas se divide entre um caminho de código para tipos que podem ser null
(deve retornar null
se nenhum elemento encontrado e não deve fazer comparações para encontrar o mínimo se um dos elementos comparados for null
) e o caminho do código para tipos que não podem ser null
(devem ser lançados se nenhum elemento for encontrado e não precisam se preocupar com a possibilidade de null
elementos )
Como TSource
é "real" dentro do método, essa comparação pode ser feita em tempo de execução ou em tempo de execução (geralmente tempo de execução, certamente o caso acima faria isso no momento da execução e não produziria código de máquina para o caminho não percorrido) e temos versão "real" separada do método para cada caso. (Embora como uma otimização, o código da máquina seja compartilhado para diferentes métodos para diferentes parâmetros do tipo de referência, porque pode ser sem afetar isso e, portanto, podemos reduzir a quantidade de código da máquina emitida).
(Não é comum falar sobre reificação de tipos genéricos em C #, a menos que você também lide com Java, porque em C # nós consideramos essa reificação como garantida; todos os tipos são reificados. Em Java, tipos não genéricos são referidos como reificados, porque é uma distinção entre eles e tipos genéricos).