De acordo com boxing em structs ao chamar ToString() , chamar um método não substituído em um C# struct
faz com que ele seja boxed. Isso ocorre, em última análise, por causa de como a constrained callvirt
instrução IL funciona, que, de acordo com a documentação , diz que:
Se
thisType
for um tipo de valor ethisType
não implementar,method
entãoptr
será desreferenciado, colocado em caixa e passado como o ponteiro 'this' para acallvirt
instrução do método.
No entanto, ele não explica por que é assim, especialmente se ele struct
implementa o método, ele não coloca em caixa o struct
:
Se
thisType
for um tipo de valor ethisType
implementarmethod
entãoptr
é passado sem modificações como o ponteiro 'this' para umacall
method
instrução, para a implementação demethod
porthisType
.
Por que o runtime não pode fazer a mesma coisa mesmo que o método não tenha sido implementado ou substituído pelo struct
? Isso provavelmente tem algo a ver com como o runtime chama métodos, mas não consigo descobrir.
Simplesmente, o método
object.ToString()
é escrito sob a suposição de quethis
será uma instância deobject
. (É assim, afinal, que os métodos de instância funcionam). Você não pode passar um ponteiro para uma struct e esperar que ele funcione.Todos os tipos de referência têm um cabeçalho de objeto e um método table . No entanto, structs não têm (não há necessidade). Portanto, se você apenas passar um (ponteiro para um) struct para um método que está esperando um ponteiro para um
object
, esse método provavelmente travará de maneiras excitantes e fatais.Quando você boxeia uma struct, ela é copiada para o heap, e recebe um cabeçalho de objeto e uma tabela de métodos. É por isso que uma struct boxeada é um
object
. Portanto, a maneira de chamar um método que espera umobject
, mas passa uma struct, é boxear a struct.Quando você define uma substituição
ToString
na própria estrutura, esse método espera um ponteiro para essa estrutura (e não assume que o ponteiro aponta para uma tabela de métodos), então você pode passar uma estrutura diretamente sem boxing.(Na verdade, quando você sobrescreve
ToString
uma struct, o tempo de execução emite dois métodos. Um é o método que você definiu, que esperathis
ser um ponteiro para uma struct, e o outro é um "thunk", que esperathis
ser umobject
(struct em caixa), que ajusta othis
ponteiro para apontar para a própria struct em vez da tabela de métodos e, em seguida, chama o primeiro método. Isso permite que você chameToString
uma struct em caixa. Não vou repetir os detalhes sangrentos, mas veja este excelente artigo de Matt Warren).