Chamar métodos static por tipo variável é ilegal - Parte III

4/17/2008 1:12:00 PM By Felipe Pessoto

Considere agora o método não-static e não-virtual:

public class C { public void M() { /*blablabla*/ } }
public class D : C { public new void M() { /*blablabla*/ } }
public class E<T> where T : C { public static void N(T t) { t.M(); } }

Nesse contexto, uma das seguintes afirmações é verdadeira:

1) Isto é inválido.
2) E<T>.N chama C.M não importa o que T é.
3) E<C>.N chama C.M, mas E<D>.N chama D.M.

Como temos discutido, para métodos static escolhemos a opção 1. Mas com métodos por instância, nós escolhemos a 2! A maioria das pessoas acharia que o método mais derivado(opção 3) seria chamado, mas não. Por quê? Porque temos que considerar que poderíamos ter: public static void N(C t) { t.M(); } o que nos faz esperar que o método menos derivado seja sempre chamado, desde que ele não seja virtual.

Por que não a opção 3? Novamente, tem a ver com análise estática. É que na realidade o que temos em ambos os casos, static e instanciados, C.M e D.M são métodos diferentes. O "new" solicita que eles sejam dois métodos diferentes que somente compartilham o mesmo nome. Você pode pensar em cada método como se ele ocupasse uma "vaga" no objeto, tanto em static como instanciado, nós definimos duas vagas, não uma. No caso do virtual e override então nós teríamos apenas uma vaga, e o conteúdo da vaga seria determinado em tempo de execução. Mas no caso não-virtual há duas vagas diferentes.

Quando o compilador gera o código generic, ele "calcula" todas as vagas em tempo de compilação. O jitter não as altera. Na verdade, o jitter não saberia como! O jitter não tem idéia que D.M tem qualquer coisa a ver com C.M. Novamente, eles são métodos totalmente diferentes que somente coincidentemente compartilham o mesmo nome. Eles têm vagas diferentes então são métodos diferentes.

Chamar métodos static por tipo variável é ilegal - Parte II

3/13/2008 2:14:00 PM By Felipe Pessoto

Na primeira parte vimos que os métodos static não sempre determinados em tempo de compilação, e usamos este fato para justificar o porque dos métodos static não poderem ser chamados em tipos passados por parâmetro. Mas os argumentos de tipos das Generics não são determinados em tempo de compilação?

No lado de quem os chama, sim. Mas para quem é chamado, o código enviado em tempo de compilação para um método genérico é inteiramente genérico. E assim continuará até que o jitter encontre o código em tempo de execução e faça a substituição dos tipos por argumento pelos tipos passados por parâmetros.

Considere a seguinte Generic:

public class C<T> { public string M() { return typeof(T).ToString(); } }


Quando você a compila, o compilador enviará uma definição de classe genérica que diz apenas que nós temos uma classe, que tem um tipo por parâmetro, e um método que chama o ToString(). Este é todo o código que é enviado para esta classe em tempo de compilação.

Para deixar mais claro, quando você diz:

void N() { C<int> c = new C<int>(); string s = c.M(); //...

O compilador não cria uma cópia da classe no código IL com o T substituído por int.

Em vez disso, o que acontece é que quando seu método N é jittado, o jitter diz, Hey, Eu preciso traduzir C<int>.M. Neste ponto o jitter consome o IL genérico emitido pelo C<T>.M e cria um novo código x86 (ou o que for) com int substituindo T.

Diferentemente dos templates no C++ que não definem tipos genéricos. Em vez disso, templates C++ são basicamente um atalho em tempo de compilação, como um complexo buscar-e-substituir. Se você dizer C<int> no C++, então em tempo de compilação, o compilador C++ textualmente irá substituir int pelo T e enviar o código como se você tivesse escrito a classe usando o int.

Se o C# tivesse templates em vez de macros então um método static chamado em um template realmente seria determinado em tempo de compilação, porque a classe inteira seria processada em tempo de compilação. Este sentido de templates é um mecanismo mais poderoso que generics – você pode fazer coisas loucas com templates porque eles não são "type safety" imposto sobre o modelo como um todo. Pelo contrário, o "type safety" só está marcado para cada construção do template efetivamente no programa.

Mas tipos genéricos no C# não são templates, eles devem ser "typesafe" dada qualquer possível construção a qual satisfaça as regras, não somente sobre um conjunto que está atualmente construído em um programa em particular.

Chamar métodos static por tipo variável é ilegal - Parte I

2/29/2008 12:55:00 PM By Felipe Pessoto

Considere o seguinte:

public class C { public static void M() { /*blablabla*/ } }
public class D : C { public new static void M() { /*blablabla*/ } }
public class E<T> where T : C { public static void N() { T.M(); } }

Isso é ilegal. Nós não podemos chamar um método static sob um tipo variável. A questão é, por quê? Nós sabemos que T deve ser um C, e C tem um método static M, então por que isto não é válido?

Espero que você concorde que uma das seguintes afirmações deve ser verdadeira:

1) Isto é inválido.
2) E<T>.N() chama C.M() não importa o que T é.
3) E<C>.N() chama C.M() mas E<D>.N() chama D.M().

Se nós pegarmos a opção 2, então isto é tão sem sentido quanto totalmente inútil. O usuário irá esperar que D.M() seja chamado se T for um D. Porque senão para que teriamos o trabalho de chamar T.M() em vez de C.M() se ele irá "sempre chamar C.M()"?

Se pegarmos a opção 3, então nós temos uma violação dos princípios de design do núcleo dos métodos static, o princípio que lhes deu o nome. Métodos estáticos são chamados “static” porque eles podem sempre determinar exatamente, em tempo de compilação, qual método será chamado. Ou seja, o método pode ser resolvido unicamente por análise estática do código.

Só sobra a opção 1.

Perguntas relacionadas são frequentemente abordadas, de várias formas. Muitas pessoas costumam perguntar porque C# não suporta métodos “virtual static”. Mas o que elas estão querendo dizer? Pois “virtual” e “static” são opostos! “virtual” significa“determinar o método a ser chamado baseado nas informações de tipo em tempo de execução”, e “static” significa“determinar o método a ser chamado unicamente baseado na análise estática em tempo de compilação”.

Na verdade o que as pessoas querem é um outro tipo de método, que não seja static, instanciado ou virtual. Nós poderiamos chegar à uma nova espécie de método que se comporta como a opção 3. Isto é, um método associado com o tipo (como um static), que não tem um argumento this "non-nullable" (diferente de um instanciado ou virtual), mas um onde o método chamado deve depender do tipo construído sobre o T (diferente de um static, o qual deve ser determinado em tempo de compilação).