2012-04-06 11 views
13

La pregunta principal es cuáles son las implicaciones de permitir que esta palabra clave se modifique con respecto a la utilidad y la memoria; y ¿por qué está permitido esto en las especificaciones del lenguaje C#?Asigne esta palabra clave en C#

Las otras preguntas/subpartes se pueden responder o no si se elige hacerlo. Pensé que las respuestas a ellos ayudarían a aclarar la respuesta a la pregunta principal.

me encontré con esto como una respuesta a What's the strangest corner case you've seen in C# or .NET?

public struct Teaser 
{ 
    public void Foo() 
    { 
     this = new Teaser(); 
    } 
} 

He estado tratando de envolver mi cabeza en torno a por qué las especificaciones del lenguaje C# siquiera permitir esto. Subparte 1. ¿Hay algo que justifique tener este ser modificable? ¿Es todo útil?

Una de las observaciones a esa respuesta era

De CLR a través de C#: La razón por la que hacen esto es porque puede llamar al constructor sin parámetros de una estructura en otra constructor. Si solo desea inicializar un valor de una estructura y desea que los otros valores sean cero/nulo (valor predeterminado), puede escribir public Foo (barra int) {this = new Foo(); specialVar = bar;}. Esto no es eficiente y no está realmente justificado (specialVar se asigna dos veces), pero solo FYI. (Esa es la razón dada en el libro, no sé por qué no deberíamos hacer pública Foo (barra int): este())

Sub-parte 2. No estoy Seguro que sigo ese razonamiento. ¿Alguien puede aclarar a qué se refería? ¿Tal vez un ejemplo concreto de cómo se usaría?

EDITAR (El punto principal de la pila o el montón de desaprobación es la liberación de memoria o la recolección de basura.) En lugar de la int [] se puede reemplazar eso con 262144 campos públicos públicos) También entendí que las estructuras se crean en la pila en oposición a la pila si esta estructura fuera a tener un campo de matriz de bytes 1 Mb inicializa como así

public int[] Mb = new int[262144]; 

Sub-parte 3. tiene esto nunca se eliminan de la pila cuando se llama Foo? Para mí, parece que como la estructura nunca salió de su alcance, no se eliminaría de la pila. No tengo tiempo esta noche para crear un caso de prueba, pero tal vez lo haga para este mañana.

En el código de abajo

Teaser t1 = new Teaser(); 
Teaser tPlaceHolder = t1; 
t1.Foo(); 

Sub-parte 4. ¿Están t1 y tPlaceHolder ocupando el mismo o diferente espacio de direcciones?

Perdón por tener una publicación de 3 años, pero esta me ha sacado la cabeza.

FYI primera pregunta en stackoverflow por lo que si tengo algo mal con la pregunta amablemente publique un comentario y voy a editar.

Después de 2 días pondré una recompensa de 50 en esta pregunta, incluso si ya tengo un ganador en mente, ya que creo que la respuesta requerirá una cantidad razonable de trabajo para explicar las preguntas.

+3

4 preguntas en 1 no son apropiadas para Desbordamiento de pila. Una pregunta * debe ser una pregunta. * –

+4

@AnthonyPegram - En este caso, creo que es aceptable, ya que estas preguntas son preguntas secundarias sobre cómo funcionan realmente las estructuras – Polity

+1

Mis disculpas no estoy seguro de cómo convertir todo en una. La pregunta principal que supongo se puede resumir por ¿cuáles son los efectos secundarios de permitir que esta palabra clave se modifique? Las preguntas 3-4 son efectos secundarios que pensé que podrían ser una posibilidad. Si alguien tiene poder para editar una pregunta además de mí y cree que pueden preguntarla mejor, siéntase libre de hacerlo. O deje un comentario sugiriendo una mejor pregunta y comenzaré de cero. –

Respuesta

6

Antes que nada, creo que deberías empezar por examinar si estás haciendo la pregunta correcta. Tal vez deberíamos preguntarnos, "¿Por qué C# no permitir la asignación a this en una estructura?"

La asignación de la palabra clave this en un tipo de referencia es potencialmente peligrosa: está sobreescribiendo una referencia al objeto cuyo método está ejecutando, incluso podría estar dentro del constructor que está inicializando esa referencia. No está claro cuál es el comportamiento de eso. Para evitar tener que averiguarlo, dado que no es generalmente útil, no está permitido por la especificación (o compilador).

Asignando a la palabra clave this en un tipo de valor, sin embargo , está bien definido. La asignación de tipos de valores es una operación de copia. El valor de cada campo se copia recursivamente del lado derecho al izquierdo de la asignación. Esta es una operación perfectamente segura en una estructura, incluso en un constructor, porque Si la copia original de la estructura aún está presente, solo está cambiando sus datos. Es exactamente equivalente a configurar manualmente cada campo en la estructura. ¿Por qué la especificación o el compilador deben prohibir una operación que esté bien definida y sea segura?

Esto, por cierto, responde una de sus subpreguntas. La asignación del tipo de valor es una operación de copia profunda, no una copia de referencia. Teniendo en cuenta este código:

Teaser t1 = new Teaser(); 
Teaser tPlaceHolder = t1; 
t1.Foo(); 

Usted ha asignado dos copias de su estructura Teaser, y copiado los valores de los campos de la primera en los campos en la segunda. Esta es la naturaleza de los tipos de valores: dos tipos que tienen campos idénticos son idénticos, al igual que dos variables int que contienen 10 son idénticas, independientemente de dónde estén "en la memoria".

Además, esto es importante y vale la pena repetirlo: hacer suposiciones cuidadosas sobre lo que sucede en "la pila" contra "el montón". Los tipos de valores terminan en el montón todo el tiempo, según el contexto en el que se usan. Las estructuras de vida corta (alcance local) que no están cerradas o que de otro modo se sacaron de su ámbito de aplicación son bastante propensas a ser asignadas a la pila. Pero eso es un sin importancia implementation detail del que no debe preocuparse ni confiar. La clave es que son tipos de valores y se comportan como tales.

En cuanto a la utilidad de la asignación a this realmente es: no muy. Ya se han mencionado casos de uso específicos. Puede usarlo para inicializar principalmente una estructura con valores predeterminados, pero especifique un número pequeño. Puesto que usted está obligado a establecer todos los campos antes de su declaración de constructor, esto puede ahorrar una gran cantidad de código redundante:

public struct Foo 
{ 
    // Fields etc here. 

    public Foo(int a) 
    { 
    this = new Foo(); 
    this.a = a; 
    } 
} 

También se puede utilizar para realizar una operación de intercambio rápido:

public void SwapValues(MyStruct other) 
{ 
    var temp = other; 
    other = this; 
    this = temp; 
} 

Más allá de eso , es solo un efecto secundario interesante del lenguaje y la forma en que se implementan las estructuras y los tipos de valor que probablemente nunca necesitarás conocer.

+0

+1 para intentar antes de que se cerrara. Intentaré reescribir esta pregunta en algún momento mañana. –

+0

Decidí votar esta es la respuesta y no volver a abrir una nueva. La copia por valor es lo que hizo que finalmente se asimilara. Creo que en mi mente siempre he pensado en "esto" como referencia y no estaba pensando en términos de "esto" como una referencia al tipo de valor cuando se trata de estructuras. No creo ni siquiera utilicé la palabra clave this en cualquiera de las estructuras que he hecho. –

1

Tener esta asignación permite casos de esquina 'avanzados' con estructuras. Un ejemplo que encontré fue un método de intercambio:

struct Foo 
{ 
    void Swap(ref Foo other) 
    { 
     Foo temp = this; 
     this = other; 
     other = temp; 
    } 
} 

Me gustaría fuertemente argumentar en contra de este uso ya que viola el valor predeterminado 'deseado' naturaleza de una estructura que es la inmutabilidad. La razón para tener esta opción está discutiblemente poco clara.

Ahora, cuando se trata de structs ellos mismos. Se diferencian de las clases de varias formas:

  • Pueden vivir en la pila en lugar del montón administrado.
  • Se pueden ordenar de nuevo a código no administrado.
  • No se pueden asignar a un valor NULO.

Para una descripción completa, véase: http://www.jaggersoft.com/pubs/StructsVsClasses.htm

relativa a su pregunta es si su estructura vive en la pila o el montón. Esto está determinado por la ubicación de asignación de una estructura. Si la estructura es un miembro de una clase, se asignará en el montón. De lo contrario, si se asigna una estructura directamente, se asignará en el montón (en realidad, esto es solo una parte de la imagen. Este conjunto se volverá bastante complejo una vez que comience a hablar sobre cierres introducidos en C# 2.0 pero por ahora es suficiente para responde tu pregunta).

Una matriz en .NET se asigna de manera predeterminada en el montón (este comportamiento no es coherente cuando se usa un código no seguro y la palabra clave stackalloc). Volviendo a la explicación anterior, eso indicaría que las instancias struct también se asignan en el montón. De hecho, una forma fácil de probar esto es asignando una matriz de 1 mb de tamaño y observar cómo se produce NO excepción de stackoverflow.

La duración de una instancia en la pila está determinada por su alcance. Esto es diferente de una instancia en el montón de administrador cuya duración está determinada por el recolector de elementos no utilizados (y si todavía hay referencias a esa instancia). Puedes asegurarte de que cualquier cosa en la pila viva mientras esté dentro del alcance. Asignar una instancia en la pila y llamar a un método no desasignará esa instancia hasta que dicha instancia salga del alcance (de forma predeterminada, cuando el método en el que se declaró dicha instancia finalice).

Una estructura no tiene referencias gestionadas (los punteros son posibles en código no administrado). Cuando trabajas con estructuras en la pila en C#, básicamente tienes una etiqueta para una instancia en lugar de una referencia. Asignar una estructura a otra simplemente copia los datos subyacentes. Puedes ver las referencias como estructuras. Naively put, una referencia no es más que una estructura que contiene un puntero a una determinada parte en la memoria. Al asignar una referencia a la otra, los datos del puntero se copian.

// declare 2 references to instances on the managed heap 
var c1 = new MyClass(); 
var c2 = new MyClass(); 

// declare 2 labels to instances on the stack 
var s1 = new MyStruct(); 
var s2 = new MyStruct(); 

c1 = c2; // copies the reference data which is the pointer internally, c1 and c2 both point to the same instance 
s1 = s2; // copies the data which is the struct internally, c1 and c2 both point to their own instance with the same data 
+0

Supongo que independientemente de que se haya copiado en la pila o en el Al llamar a Foo, ¿el GC finalmente limpiará la memoria asociada con la estructura original? Solo para aclararlo, estoy hablando de un código estrictamente administrado. Ohh y por Foo quise decir que el Foo en la estructura Teaser no es tu estructura Foo. –

+0

El GC solo limpiará instancias asignadas en el montón administrado. Las instancias basadas en pila en realidad no están realmente limpias (por lo tanto, una estructura no puede contener un destructor). Su etiqueta simplemente se volverá inválida una vez fuera del alcance. Ver la respuesta de eric lippert en esta publicación: http://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope – Polity

+0

las estructuras son tipos de valores; nunca son explícitamente basura recolectada. La memoria para tipos de valor se devuelve al sistema operativo cuando el tipo de valor deja alcance; si se trata de una variable o parámetro local, esto sucede y la función vuelve. Si se trata de un campo, sucede cuando su clase contenedor es GC'd. Etc. –