Um inicializador de campo não pode fazer referência ao campo, método ou propriedade não estática


92

Eu tenho uma aula e quando tento utilizá-la em outra aula recebo o erro abaixo.

using System;
using System.Collections.Generic;
using System.Linq;

namespace MySite
{
    public class Reminders
    {
        public Dictionary<TimeSpan, string> TimeSpanText { get; set; }

        // We are setting the default values using the Costructor
        public Reminders()
        {
            TimeSpanText.Add(TimeSpan.Zero, "None");
            TimeSpanText.Add(new TimeSpan(0, 0, 5, 0), "5 minutes before");
            TimeSpanText.Add(new TimeSpan(0, 0, 15, 0), "15 minutes before");
            TimeSpanText.Add(new TimeSpan(0, 0, 30, 0), "30 minutes before");
            TimeSpanText.Add(new TimeSpan(0, 1, 0, 0), "1 hour before");
            TimeSpanText.Add(new TimeSpan(0, 2, 0, 0), "2 hours before");
            TimeSpanText.Add(new TimeSpan(1, 0, 0, 0), "1 day before");
            TimeSpanText.Add(new TimeSpan(2, 0, 0, 0), "2 day before");
        }

    }
}

Usando a classe em outra classe

class SomeOtherClass
{  
    private Reminders reminder = new Reminders();
    // error happens on this line:
    private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 
    ....

Erro (CS0236):

A field initializer cannot reference the nonstatic field, method, or property

Por que isso acontece e como consertar?

Respostas:


143

Está linha:

private dynamic defaultReminder = 
                          reminder.TimeSpanText[TimeSpan.FromMinutes(15)];

Você não pode usar uma variável de instância para inicializar outra variável de instância. Por quê? Como o compilador pode reorganizá-los - não há garantia de que reminderserá inicializado antes defaultReminder, então a linha acima pode gerar um NullReferenceException.

Em vez disso, basta usar:

private dynamic defaultReminder = TimeSpan.FromMinutes(15);

Como alternativa, configure o valor no construtor:

private dynamic defaultReminder;

public Reminders()
{
    defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 
}

Há mais detalhes sobre esse erro do compilador no MSDN - Erro do compilador CS0236 .


3
Java é mais 'indulgente' para esse tipo de construção. Não sei se isso é bom. stackoverflow.com/questions/1494735/…
Wouter Schut

31
Não, o compilador não pode reorganizar os inicializadores. A especificação da linguagem C # declara, na seção "10.5.5.2 Inicialização do campo de instância", o seguinte: Os inicializadores de variáveis ​​são executados na ordem textual em que aparecem na declaração da classe. Isso é até repetido em "10.11.2 Inicializadores de variável de instância", onde se diz: Os inicializadores de variável são executados na ordem textual em que aparecem na declaração de classe. Portanto, sua explicação está errada. O pedido está fixo. A razão pela qual não é permitido é que os designers do C # queriam assim.
Jeppe Stig Nielsen

(Apenas no caso de a partial classcom "partes" em vários arquivos a ordem dos inicializadores de campo não está clara, mas isso vale para os staticcampos também!)
Jeppe Stig Nielsen

@WouterSchut O tópico que você linkou não é sobre Java ?! É sobre C # também, porém com staticcampos em vez de campos de instância.
Jeppe Stig Nielsen

2
@Andrew Não é verdade, muitas decisões são tomadas para proibir práticas inadequadas. mesmo que eles possam, teoricamente, ser implementados, alguns protegidos por Avisos e alguns são Erros simples. e acho que esse é um desses casos ... embora o padrão diga que é sequencial, mesmo um desenvolvedor experiente não diria com segurança (sem pesquisar o padrão).
Tomer W

22

Você precisa colocar esse código no construtor de sua classe:

private Reminders reminder = new Reminders();
private dynamic defaultReminder;

public YourClass()
{
    defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];
}

A razão é que você não pode usar uma variável de instância para inicializar outra usando um inicializador de campo.


9

você pode usar assim

private dynamic defaultReminder => reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 

10
Bem-vindo ao Stack Overflow! Embora este snippet de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade de sua postagem. Lembre-se de que você está respondendo à pergunta para leitores no futuro e essas pessoas podem não saber os motivos de sua sugestão de código. Tente também não sobrecarregar seu código com comentários explicativos, pois isso reduz a legibilidade do código e das explicações!
jmattheis

3
Ele está usando => em vez de =, tornando-o assim uma propriedade.
Vincent

2
Tenha cuidado ao usar esta técnica, já que usar =>não define o valor real, mas executará o código toda vez que defaultReminderfor acessado. Isso pode não ser intencional e ter um impacto negativo no desempenho ou gerar pressão indesejada para GC, etc.
Smilediver

5

private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];é um inicializador de campo e é executado primeiro (antes que qualquer campo sem um inicializador seja definido com seu valor padrão e antes que o construtor de instância invocado seja executado). Os campos de instância que não têm inicializador só terão um valor legal (padrão) depois que todos os inicializadores de campo de instância forem concluídos. Devido à ordem de inicialização, os construtores de instância são executados por último, razão pela qual a instância ainda não foi criada no momento em que os inicializadores são executados. Portanto, o compilador não pode permitir que qualquer propriedade (ou campo) da instância seja referenciada antes que a instância da classe seja totalmente construída. Isso ocorre porque qualquer acesso a uma variável de instância, como reminderimplicitamente, faz referência à instância ( this) para informar ao compilador a localização concreta da memória da instância a ser usada.

Esse também é o motivo pelo qual thisnão é permitido em um inicializador de campo de instância.

Um inicializador de variável para um campo de instância não pode fazer referência à instância que está sendo criada. Portanto, é um erro em tempo de compilação fazer referência a isso em um inicializador de variável, pois é um erro em tempo de compilação para um inicializador de variável fazer referência a qualquer membro de instância por meio de um simple_name .

Os únicos membros de tipo que têm garantia de inicialização antes que os inicializadores de campo de instância sejam executados são inicializadores de campo de classe (estático) e construtores de classe (estáticos) e métodos de classe. Como os membros estáticos são independentes da instância, eles podem ser referenciados a qualquer momento:

class SomeOtherClass
{
  private static Reminders reminder = new Reminders();

  // This operation is allowed,
  // since the compiler can guarantee that the referenced class member is already initialized
  // when this instance field initializer executes
  private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];
}

É por isso que os inicializadores de campo de instância só podem fazer referência a um membro de classe (membro estático). Essas regras de inicialização do compilador garantirão uma instanciação de tipo determinístico.

Para mais detalhes, recomendo este documento: Microsoft Docs: Declarações de classe .

Isso significa que um campo de instância que faz referência a outro membro de instância para inicializar seu valor deve ser inicializado a partir do construtor de instância ou o membro referenciado deve ser declarado static.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.