Eu sei que estruturas no .NET não suportam herança, mas não está exatamente claro por que elas são limitadas dessa maneira.
Que razão técnica impede que as estruturas sejam herdadas de outras estruturas?
.net
inheritance
struct
Julieta
fonte
fonte
Respostas:
A razão pela qual os tipos de valor não podem suportar herança é por causa de matrizes.
O problema é que, por razões de desempenho e GC, matrizes de tipos de valor são armazenadas "em linha". Por exemplo, dado que
new FooType[10] {...}
, seFooType
for um tipo de referência, 11 objetos serão criados no heap gerenciado (um para a matriz e 10 para cada instância de tipo). Se, emFooType
vez disso, for um tipo de valor, apenas uma instância será criada no heap gerenciado - para a própria matriz (pois cada valor da matriz será armazenado "em linha" com a matriz).Agora, suponha que tivéssemos herança com tipos de valor. Quando combinado com o comportamento acima de "armazenamento embutido" de matrizes, Bad Things acontece, como pode ser visto em C ++ .
Considere este código pseudo-C #:
Pelas regras normais de conversão, a
Derived[]
é conversível em aBase[]
(para melhor ou para pior), portanto, se você s / struct / class / g no exemplo acima, ele será compilado e executado conforme o esperado, sem problemas. Mas seBase
eDerived
são tipos de valor e matrizes armazenam valores em linha, então temos um problema.Temos um problema porque
Square()
não sabemos nadaDerived
, ele usará apenas a aritmética do ponteiro para acessar cada elemento da matriz, incrementando em uma quantidade constante (sizeof(A)
). A assembléia seria vagamente como:(Sim, é uma montagem abominável, mas o ponto é que incrementaremos a matriz em constantes conhecidas em tempo de compilação, sem o conhecimento de que um tipo derivado está sendo usado.)
Portanto, se isso realmente acontecesse, teríamos problemas de corrupção de memória. Especificamente, dentro
Square()
,values[1].A*=2
seria realmente estar modificandovalues[0].B
!Tente depurar ISSO !
fonte
Imagine estruturas suportadas herança. Então declarando:
significaria que variáveis de estrutura não têm tamanho fixo, e é por isso que temos tipos de referência.
Melhor ainda, considere o seguinte:
fonte
Foo
herança de structBar
não deve permitir que umFoo
seja atribuído a umBar
, mas declarar uma estrutura dessa maneira pode permitir alguns efeitos úteis: (1) Crie um membro do tipo especialmente nomeadoBar
como o primeiro itemFoo
eFoo
inclua nomes de membros com nomes alternativos para esses membrosBar
, permitindo que o código que antesBar
era adaptado usasse umFoo
, sem precisar substituir todas as referênciasthing.BarMember
porthing.theBar.BarMember
e mantendo a capacidade de ler e gravar todosBar
os campos como um grupo; ...Estruturas não usam referências (a menos que estejam na caixa, mas você deve tentar evitar isso), portanto, o polimorfismo não é significativo, pois não há indireto por meio de um ponteiro de referência. Os objetos normalmente vivem no heap e são referenciados por ponteiros de referência, mas as estruturas são alocadas na pilha (a menos que estejam encaixotadas) ou alocadas "dentro" da memória ocupada por um tipo de referência no heap.
fonte
Foo
que tenha um campo de tipo de estruturaBar
capaz de considerarBar
os membros de si mesmos, para que umaPoint3d
classe poderia, por exemplo, encapsular umPoint2d xy
mas se referir aoX
desse campo como umxy.X
ouX
.Aqui está o que os documentos dizem:
Basicamente, eles devem conter dados simples e, portanto, não possuem "recursos extras", como herança. Provavelmente seria tecnicamente possível para eles oferecerem suporte a algum tipo limitado de herança (não polimorfismo, por estarem na pilha), mas acredito que também é uma opção de design para não dar suporte à herança (como muitas outras coisas no .NET idiomas são.)
Por outro lado, concordo com os benefícios da herança e acho que todos atingimos o ponto em que queremos
struct
que herdemos do outro e compreendemos que isso não é possível. Mas, nesse ponto, a estrutura de dados provavelmente é tão avançada que deveria ser uma classe de qualquer maneira.fonte
Point3D
partir dePoint2D
; você não seria capaz para usar umPoint3D
em vez de umPoint2D
, mas você não teria que reimplementar oPoint3D
inteiramente a partir do zero) É assim que eu interpretada de qualquer maneira ....class
sobrestruct
quando apropriado.Classe como herança não é possível, pois uma estrutura é colocada diretamente na pilha. Uma estrutura herdada seria maior que a mãe, mas o JIT não sabe disso e tenta colocar muito menos espaço. Parece um pouco claro, vamos escrever um exemplo:
Se isso fosse possível, haveria um erro fatal no seguinte trecho:
O espaço é alocado para o tamanho de A, não para o tamanho de B.
fonte
Há um ponto que eu gostaria de corrigir. Embora a razão pela qual as estruturas não possam ser herdadas seja porque elas vivem na pilha é a correta, é ao mesmo tempo uma explicação parcialmente correta. Estruturas, como qualquer outro tipo de valor, podem viver na pilha. Como isso dependerá de onde a variável é declarada, eles viverão na pilha ou na pilha . Será quando eles forem variáveis locais ou campos de instância, respectivamente.
Ao dizer isso, Cecil Has a Name acertou em cheio.
Eu gostaria de enfatizar isso, os tipos de valor podem viver na pilha. Isso não significa que eles sempre fazem isso. Variáveis locais, incluindo parâmetros de método, serão. Todos os outros não. Ainda assim, continua sendo a razão pela qual eles não podem ser herdados. :-)
fonte
As estruturas são alocadas na pilha. Isso significa que a semântica de valores é praticamente gratuita e o acesso a membros de estrutura é muito barato. Isso não impede o polimorfismo.
Você pode iniciar cada estrutura com um ponteiro para sua tabela de funções virtuais. Isso seria um problema de desempenho (cada estrutura teria pelo menos o tamanho de um ponteiro), mas é factível. Isso permitiria funções virtuais.
Que tal adicionar campos?
Bem, quando você aloca uma estrutura na pilha, aloca uma certa quantidade de espaço. O espaço necessário é determinado no tempo de compilação (antecipadamente ou ao JITting). Se você adicionar campos e depois atribuir a um tipo de base:
Isso substituirá uma parte desconhecida da pilha.
A alternativa é que o tempo de execução impeça isso gravando apenas sizeof (A) bytes em qualquer variável A.
O que acontece se B substituir um método em A e referenciar seu campo Integer2? O tempo de execução lança uma MemberAccessException ou o método acessa alguns dados aleatórios na pilha. Nenhuma delas é permitida.
É perfeitamente seguro ter herança de estrutura, desde que você não use estruturas polimorficamente ou contanto que você não adicione campos ao herdar. Mas estes não são terrivelmente úteis.
fonte
Esta parece ser uma pergunta muito frequente. Eu sinto como adicionar que os tipos de valor são armazenados "no local" onde você declara a variável; além dos detalhes da implementação, isso significa que não há um cabeçalho de objeto que diga algo sobre o objeto, apenas a variável sabe que tipo de dados reside lá.
fonte
As estruturas suportam interfaces, para que você possa fazer algumas coisas polimórficas dessa maneira.
fonte
IL é uma linguagem baseada em pilha, portanto, chamar um método com um argumento é algo como isto:
Quando o método é executado, ele libera alguns bytes da pilha para obter seu argumento. Ele sabe exatamente quantos bytes serão disparados porque o argumento é um ponteiro de tipo de referência (sempre 4 bytes em 32 bits) ou é um tipo de valor para o qual o tamanho sempre é conhecido exatamente.
Se for um ponteiro de tipo de referência, o método pesquisará o objeto no heap e obterá seu identificador de tipo, que aponta para uma tabela de métodos que manipula esse método específico para esse tipo exato. Se for um tipo de valor, nenhuma pesquisa será necessária em uma tabela de métodos, porque os tipos de valor não suportam herança, portanto, existe apenas uma combinação possível de método / tipo.
Se os tipos de valor suportassem herança, haveria uma sobrecarga extra, pois o tipo específico da estrutura teria que ser colocado na pilha, bem como seu valor, o que significaria algum tipo de pesquisa de tabela de método para a instância concreta específica do tipo. Isso eliminaria as vantagens de velocidade e eficiência dos tipos de valor.
fonte