Pergunta

Eu tenho uma pergunta sobre construtores de tipo dentro de um tipo Value.Esta questão foi inspirada em algo que Jeffrey Richter escreveu em CLR via C# 3ª ed, ele diz (na página 195 - capítulo 8) que você nunca deve realmente definir um construtor de tipo dentro de um tipo de valor, pois há momentos em que o CLR não chamará isto.

Então, por exemplo (bem... exemplo de Jeffrey Richters, na verdade), não consigo entender, mesmo olhando para o IL, por que o construtor de tipo não está sendo chamado no código a seguir:

internal struct SomeValType
{
    static SomeValType()
    {
        Console.WriteLine("This never gets displayed");
    }
    public Int32 _x;
}
public sealed class Program
{
    static void Main(string[] args)
    {
        SomeValType[] a = new SomeValType[10];
        a[0]._x = 123;
        Console.WriteLine(a[0]._x);     //Displays 123
    }
}

Portanto, aplicando as seguintes regras para construtores de tipo, simplesmente não consigo entender por que o construtor de tipo de valor acima não é chamado.

  1. Posso definir um construtor de tipo de valor estático para definir o estado inicial do tipo.
  2. Um tipo não pode ter mais de um construtor - não existe um construtor padrão.
  3. Construtores de tipo são implicitamente privados
  4. O compilador JIT verifica se o construtor de tipo do tipo já foi executado neste AppDomain.Caso contrário, ele emite a chamada para o código nativo, caso contrário, não o faz, pois sabe que o tipo já está 'inicializado'.

Então...simplesmente não consigo entender por que não consigo ver esse tipo de array sendo construído.

Meu melhor palpite seria que poderia ser:

  1. A maneira como o CLR constrói um array de tipos.Eu teria pensado que o construtor estático seria chamado quando o primeiro item fosse criado
  2. O código no construtor não está inicializando nenhum campo estático, portanto é ignorado.Eu experimentei inicializar campos estáticos privados dentro do construtor, mas o campo permanece com o valor padrão 0 - portanto, o construtor não é chamado.
  3. Ou... o compilador está de alguma forma otimizando a chamada do construtor devido ao Int32 público estar definido - mas isso é, na melhor das hipóteses, um palpite confuso!!

Deixando de lado as melhores práticas, etc., estou super intrigado com isso, pois quero poder ver por mim mesmo por que ele não é chamado.

EDITAR:Adicionei uma resposta à minha pergunta abaixo, apenas uma citação do que Jeffrey Richter diz sobre isso.

Se alguém tiver alguma idéia, isso seria brilhante.Muito obrigado, James

Foi útil?

Solução

O Especificação do Microsoft C#4 mudou ligeiramente em relação às versões anteriores e agora reflete com mais precisão o comportamento que estamos vendo aqui:

11.3.10 Construtores estáticos

Construtores estáticos para estruturas seguem a maioria das mesmas regras das classes.A execução de um construtor estático para um tipo de estrutura é desencadeado pelo primeiro dos seguintes eventos a ocorrer dentro de um domínio de aplicação:

  • Um membro estático do tipo struct é referenciado.
  • Um construtor explicitamente declarado do tipo struct é chamado.

A criação de valores padrão (§11.3.4) dos tipos de estrutura não aciona o construtor estático.(Um exemplo disso é o valor inicial dos elementos em uma matriz.)

O Especificação ECMA e a Especificação do Microsoft C#3 ambos têm um evento extra nessa lista:"Um membro de instância do tipo struct é referenciado". Portanto, parece que o C#3 violou suas próprias especificações aqui.A especificação C#4 foi alinhada com o comportamento real do C#3 e 4.

EDITAR...

Após uma investigação mais aprofundada, parece que praticamente todos os acessos dos membros da instância exceto o acesso direto ao campo acionará o construtor estático (pelo menos nas implementações atuais da Microsoft de C#3 e 4).

Portanto, as implementações atuais estão mais correlacionadas com as regras fornecidas nas especificações ECMA e C#3 do que aquelas nas especificações C#4:as regras C#3 são implementadas corretamente ao acessar todos os membros da instância exceto Campos;as regras C # 4 são apenas implementado corretamente para acesso ao campo.

(As diferentes especificações estão todas de acordo - e aparentemente implementadas corretamente - quando se trata de regras relacionadas ao acesso a membros estáticos e construtores declarados explicitamente.)

Outras dicas

Do §18.3.10 da norma (ver também A linguagem de programação C# livro):

A execução de um construtor estático para uma estrutura é acionada pelo primeiro dos seguintes eventos que ocorre dentro de um domínio de aplicativo:

  • Um membro da instância da estrutura é referenciado.
  • Um membro estático da estrutura é referenciado.
  • Um construtor explicitamente declarado da estrutura é chamado.

[Observação:A criação dos valores padrão (§18.3.4) de struct tipos não aciona a estática construtor.(Um exemplo disso é o valor inicial dos elementos em um matriz.) nota final]

Portanto, eu concordaria com você que as duas últimas linhas do seu programa deveriam acionar, cada uma, a primeira regra.

Após o teste, o consenso parece ser que ele aciona consistentemente métodos, propriedades, eventos e indexadores.Isso significa que está correto para todos os membros explícitos da instância exceto Campos.Portanto, se as regras C# 4 da Microsoft fossem escolhidas para o padrão, isso faria com que sua implementação passasse de quase certa para quase errada.

Apenas colocando isso como uma 'resposta' para que eu pudesse compartilhar o que o próprio Sr. Richter escreveu sobre isso (a propósito, alguém tem um link para a especificação CLR mais recente, é fácil obter a edição de 2006, mas é um pouco mais difícil de obter). obtenha o mais recente):

Para esse tipo de coisa, geralmente é melhor observar a especificação CLR do que a especificação C#.A especificação CLR diz:

4.Se não estiver marcado BeforeFieldInit então o método inicializador desse tipo é executado em (ou seja, é acionado por):

• primeiro acesso a qualquer campo estático desse tipo, ou

• primeira invocação de qualquer método estático desse tipo ou

• primeira invocação de qualquer instância ou método virtual desse tipo, se for um tipo de valor ou

• primeira invocação de qualquer construtor para esse tipo.

Como nenhuma dessas condições é satisfeita, o construtor estático é não invocado.As únicas partes complicadas a serem observadas são que “_x” é um campo de instância, não um campo estático, e construir um array de estruturas não não invocar quaisquer construtores de instância nos elementos da matriz.

Outra amostra interessante:

   struct S
    {
        public int x;
        static S()
        {
            Console.WriteLine("static S()");
        }
        public void f() { }
    }

    static void Main() { new S().f(); }

Atualizar: minha observação é que, a menos que o estado estático seja usado, o construtor estático nunca será tocado - algo que o tempo de execução parece decidir e não se aplica aos tipos de referência.Isso levanta a questão se é um bug restante porque tem pouco impacto, é intencional ou é um bug pendente.

Atualização 2: pessoalmente, a menos que você esteja fazendo algo estranho no construtor, esse comportamento do tempo de execução nunca deve causar problemas.Assim que você acessa o estado estático, ele se comporta corretamente.

Atualização3: após um comentário de LukeH, e referenciando a resposta de Matthew Flaschen, implementar e chamar seu próprio construtor na estrutura também aciona a chamada do construtor estático.O que significa que em um dos três cenários, o comportamento não é o que está escrito na lata.

Acabei de adicionar uma propriedade estática ao tipo e acessei essa propriedade estática - ela é chamada de construtor estático.Sem o acesso da propriedade estática, apenas criando uma nova instância do tipo, o construtor estático não foi chamado.

internal struct SomeValType
    {
        public static int foo = 0;
        public int bar;

        static SomeValType()
        {
            Console.WriteLine("This never gets displayed");
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Doesn't hit static constructor
            SomeValType v = new SomeValType();
            v.bar = 1;

            // Hits static constructor
            SomeValType.foo = 3;
        }
    }

Uma nota neste link especifica que construtores estáticos são não chamado ao simplesmente acessar instâncias:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default

Eu acho que você está criando um ARRAY do seu tipo de valor.Portanto, a palavra-chave new seria usada para inicializar a memória do array.

É válido dizer

SomeValType i;
i._x = 5;

sem nenhuma nova palavra-chave em nenhum lugar, que é essencialmente o que você está fazendo aqui.Se SomeValType fosse um tipo de referência, você teria que inicializar cada elemento do seu array com

array[i] = new SomeRefType();

Este é um comportamento maluco por design do atributo "beforefieldinit" no MSIL.Isso também afeta C++/CLI, apresentei um relatório de bug onde a Microsoft explicou muito bem por que o comportamento é daquele jeito e apontei várias seções no padrão de linguagem que não concordavam/precisavam ser atualizadas para descrever o comportamento real .Mas não é visível publicamente.De qualquer forma, aqui está a palavra final da Microsoft (discutindo uma situação semelhante em C++/CLI):

Já que estamos invocando o padrão aqui, a linha da partição I, 8.9.5 diz o seguinte:

Se marcado BeforeFieldInit, então o O método inicializador do tipo é executado em, ou algum tempo antes, primeiro acesso para qualquer campo estático definido para isso tipo.

Essa seção, na verdade, entra em detalhes Sobre como uma implementação de linguagem pode optar por impedir o comportamento você está descrevendo.C++/CLI escolhe não para, em vez disso, eles permitem que o programador para fazê-lo, se assim o desejarem.

Basicamente, já que o código abaixo tem absolutamente nenhum campo estático, o JIT é completamente correto em simplesmente não invocando construtores de classe estática.

O mesmo comportamento é o que você está vendo, embora em um idioma diferente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top