Un inicializador de campo no puede hacer referencia al campo, método o propiedad no estático

96

Tengo una clase y cuando intento usarla en otra clase, recibo el siguiente error.

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 la clase en otra clase

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

Error (CS0236):

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

¿Por qué sucede y cómo solucionarlo?

GibboK
fuente

Respuestas:

147

Esta línea:

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

No puede utilizar una variable de instancia para inicializar otra variable de instancia. ¿Por qué? Debido a que el compilador puede reorganizarlos, no hay garantía de que reminderse inicialice antes defaultReminder, por lo que la línea anterior podría arrojar un NullReferenceException.

En su lugar, solo usa:

private dynamic defaultReminder = TimeSpan.FromMinutes(15);

Alternativamente, configure el valor en el constructor:

private dynamic defaultReminder;

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

Hay más detalles sobre este error del compilador en MSDN - Error del compilador CS0236 .

Oded
fuente
3
Java es más "indulgente" para este tipo de construcciones. No sé si eso es bueno. stackoverflow.com/questions/1494735/…
Wouter Schut
32
No, el compilador no puede reorganizar los inicializadores. La Especificación del lenguaje C # establece, en la sección "10.5.5.2 Inicialización del campo de instancia", lo siguiente: Los inicializadores de variables se ejecutan en el orden textual en el que aparecen en la declaración de clase. Esto incluso se repite en "10.11.2 Inicializadores de variables de instancia" donde dicen: Los inicializadores de variables se ejecutan en el orden textual en el que aparecen en la declaración de clase. Entonces tu explicación es incorrecta. El orden es fijo. La razón por la que no está permitida es que los diseñadores de C # lo querían de esa manera.
Jeppe Stig Nielsen
(Solo en el caso de un partial classcon "partes" en varios archivos, el orden de los inicializadores de campo no está claro, ¡pero eso también se aplica a los staticcampos!)
Jeppe Stig Nielsen
@WouterSchut ¿El hilo que enlazas no es sobre Java? También se trata de C #, sin embargo, con staticcampos en lugar de campos de instancia.
Jeppe Stig Nielsen
2
@Andrew No es cierto en absoluto, se toman muchas decisiones para prohibir las malas prácticas. a pesar de que, en teoría, pueden implementarse, algunos están protegidos por advertencias y otros son errores simples. y creo que este es uno de estos casos ... aunque el estándar dice que es secuencial, incluso un desarrollador experimentado no lo diría con confianza (sin buscar el estándar).
Tomer W
22

Necesitas poner ese código en el constructor de tu clase:

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

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

La razón es que no puede usar una variable de instancia para inicializar otra usando un inicializador de campo.

Daniel Hilgarth
fuente
10

puedes usar así

private dynamic defaultReminder => reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 
Jin Wang
fuente
11
¡Bienvenido a Stack Overflow! Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo a la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código. Por favor, trate también de no llenar su código con comentarios explicativos, ya que esto reduce la legibilidad tanto del código como de las explicaciones.
jmattheis
3
Está usando => en lugar de =, lo que lo convierte en una propiedad.
Vincent
3
Tenga cuidado al usar esta técnica, ya que el uso =>no establece el valor real, pero ejecutará el código cada vez que defaultReminderse acceda. Esto puede no ser intencionado y tener un impacto negativo en el rendimiento o generar una presión no deseada para GC, etc.
Smilediver
4

private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];es un inicializador de campo y se ejecuta primero (antes de que cualquier campo sin inicializador se establezca en su valor predeterminado y antes de que se ejecute el constructor de instancia invocado). Los campos de instancia que no tienen inicializador solo tendrán un valor legal (predeterminado) después de que se completen todos los inicializadores de campo de instancia. Debido al orden de inicialización, los constructores de instancias se ejecutan en último lugar, por lo que la instancia aún no se crea en el momento en que se ejecutan los inicializadores. Por lo tanto, el compilador no puede permitir que se haga referencia a ninguna propiedad de instancia (o campo) antes de que la instancia de clase esté completamente construida. Esto se debe a que cualquier acceso a una variable de instancia como hace reminderreferencia implícitamente a la instancia ( this) para decirle al compilador la ubicación de memoria concreta de la instancia a usar.

Esta es también la razón por la thisque no está permitido en un inicializador de campo de instancia.

Un inicializador de variable para un campo de instancia no puede hacer referencia a la instancia que se está creando. Por lo tanto, es un error en tiempo de compilación hacer referencia a esto en un inicializador de variable, ya que es un error en tiempo de compilación que un inicializador de variable haga referencia a cualquier miembro de instancia a través de un simple_name .

Los únicos miembros de tipo que se garantiza que se inicializarán antes de que se ejecuten los inicializadores de campo de instancia son los inicializadores de campo de clase (estáticos) y los constructores de clase (estáticos) y los métodos de clase. Dado que los miembros estáticos son independientes de la instancia, se puede hacer referencia a ellos en cualquier 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)];
}

Es por eso que los inicializadores de campo de instancia solo pueden hacer referencia a un miembro de clase (miembro estático). Estas reglas de inicialización del compilador asegurarán una instanciación de tipo determinista.

Para más detalles recomiendo este documento: Microsoft Docs: Declaraciones de clase .

Esto significa que un campo de instancia que hace referencia a otro miembro de instancia para inicializar su valor, debe inicializarse desde el constructor de instancia o el miembro referenciado debe declararse static.

BionicCode
fuente