2012-02-15 26 views
17

Ésta es una versión simplificada de una parte de mi código:ciclo en el diseño de estructura que no existe

public struct info 
{ 
    public float a, b; 
    public info? c; 

    public info(float a, float b, info? c = null) 
    { 
     this.a = a; 
     this.b = b; 
     this.c = c; 
    } 
} 

El problema es el error Struct member 'info' causes a cycle in the struct layout. que busco estructura como el comportamiento tipo de valor. Podría simular esto usando una clase y una función de miembro de clonación, pero no veo por qué debería hacerlo.

¿Cómo es este error? La recursividad podría quizás causar una construcción para siempre en algunas situaciones similares, pero no puedo pensar de ninguna manera que podría en este caso. A continuación hay ejemplos que deberían estar bien si el programa compilara.

new info(1, 2); 
new info(1, 2, null); 
new info(1, 2, new info(3, 4)); 

edición:

La solución que utilicé fue hacer "info" una clase en lugar de una estructura y dándole una función miembro a regresar una copia que he utilizado al pasar él. En efecto, simula el mismo comportamiento que una estructura pero con una clase.

También creé la siguiente pregunta mientras buscaba una respuesta.

Value type class definition in C#?

+0

Supongo que necesitas tener al menos un constructor que no tome 'info' como parámetro? Estás usando un argumento predeterminado, pero quizás a C# no le gusta eso. ¿Qué pasa si haces dos constructores? –

+2

Simplemente conviértalo en una clase; esto no es datos de estructura –

+0

'info?' no es un * puntero * a 'información', es una copia. Si realmente necesitas esto (no deberías), ¿por qué no crear tu propio tipo anulable que es una 'clase'? Incluso podría tener operadores implícitos para convertir a 'YourNullable ' de 'Nullable '. Por supuesto, significaría un * boatload * de 'YourNullable's, probablemente eliminando cualquier bonificación (si la hubiera) por tener su clase como una 'struct' :) C#' struct's no son C 'struct' s. – Luaan

Respuesta

26

No es legal tener una estructura que se conserve como miembro. Esto se debe a que una estructura tiene tamaño fijo, y debe ser al menos tan grande como la suma de los tamaños de cada uno de sus miembros. Su tipo debería tener 8 bytes para los dos flotantes, al menos un byte para mostrar si info es nulo, más el tamaño de info. Esto da la siguiente desigualdad:

size of info >= 4 + 4 + 1 + size of info 

Esto es obviamente imposible, ya que requeriría su tipo a ser infinitamente grande.

Tiene que usar un tipo de referencia (es decir, clase). Puede hacer que su clase sea inmutable y anular Equals y GetHashCode para proporcionar un comportamiento similar al valor String.

+0

+1 buen consejo en todos los aspectos. También podría hacer que el miembro c del tipo type forzara su encajamiento (¿el constructor aún podría tomar una información?). Un poco feo, aunque – MattDavey

+0

esto realmente funciona para mí. 'información pública? c; 'no da ningún error, se comporta como referencia a la estructura y el tamaño de referencia es determinable. –

10

La razón por la que esto crea un ciclo es que Nullable<T> es en sí mismo un struct. Como se refiere a info, tiene un ciclo en el diseño (info tiene un campo de Nullable<info> y tiene un campo de info). Es esencialmente equivalente a la siguiente

public struct MyNullable<T> { 
    public T value; 
    public bool hasValue; 
} 

struct info { 
    public float a, b; 
    public MyNullable<info> next; 
} 
4

El problema real está en esta línea:

public info? c; 

Dado que este es un struct, C# necesita conocer el interior info/s diseño antes de poder producir exterior info diseño de. Y el interior info incluye una interior interna info, que a su vez incluye una interna interior interna info, y así sucesivamente. El compilador no puede producir un diseño debido a este problema de referencia circular.

Nota: info? c es una abreviatura de Nullable<info> que es en sí misma una struct.

2

No hay ninguna forma de lograr la semántica de valores variables de los elementos de tamaño variable (semánticamente, creo que lo que está buscando es tener MyInfo1 = MyInfo2 generar una nueva lista vinculada separada de la iniciada por MyInfo2). Uno podría reemplazar el info? con un info[] (que siempre sería nulo o poblado con una matriz de elemento único) o con una clase de titular que incluye una instancia de info, pero la semántica probablemente no sea lo que busca . Siguiendo MyInfo1 = MyInfo2, los cambios a MyInfo1.a no afectarían a MyInfo2.a, ni los cambios a MyInfo1.c afectarían a MyInfo2.c, pero los cambios a MyInfo1.c[0].a afectarían a MyInfo2.c[0].a.

Sería bueno si una versión futura de .net pudiera tener algún concepto de "referencias de valor", de modo que copiar una estructura no simplemente copiara todos sus campos. Hay algo de valor en el hecho de que .net no admite todas las complejidades de los constructores de copia en C++, pero también habría valor al permitir que las ubicaciones de almacenamiento de tipo 'struct' tuvieran una identidad que estaría asociada con la ubicación de almacenamiento en lugar de su contenido.

Dado que .net actualmente no es compatible con ningún concepto, sin embargo, si desea que info sea mutable, tendrá que soportar una semántica de referencia mutable (incluida la clonación protectora) o con algo raro y alocado semántica struct-class-hybrid. Una sugerencia que tendría si el rendimiento es una preocupación sería tener una clase abstracta InfoBase con los descendientes MutableInfo y ImmutableInfo, y con los siguientes miembros:

  1. AsNewFullyMutable - instancia pública - Devuelve un nuevo objeto MutableInfo, con datos copiados del original, llamando al AsNewFullyMutable en cualquier referencia anidada.

  2. AsNewMutable - instancia pública - Devuelve un nuevo objeto MutableInfo, con datos copiados de los originales, llamando AsImmutable en todas las referencias anidadas.

  3. AsNewImmutable - Protegido ejemplo - Devuelve un nuevo objeto ImmutableInfo, con los datos copiados de la orignal, llamando AsImmutable (no AsNewImmutable) en todas las referencias anidadas.

  4. AsImmutable - Público virtual - Para un ImmutableInfo, devuelva; para un MutableInfo, llame al AsNewImmutable en sí mismo.

  5. AsMutable - Público virtual - Para un MutableInfo, devuelva; para un ImmutableInfo, llame al AsNewMutable en sí mismo.

Al clonar un objeto, dependiendo de si se espera que el objeto o sus descendientes serían clonado de nuevo antes de que tuvo que ser mutado, uno podría llamar a cualquiera AsImmutable, AsNewFullyMutable o AsNewMutable. En los escenarios en los que uno esperaría que un objeto se clonara repetidamente de manera defensiva, el objeto sería reemplazado por una instancia inmutable que ya no tendría que ser clonada hasta que hubiera un deseo de mutarlo.

Cuestiones relacionadas