This is a simplified version of some of my code:
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;
}
}
The problem is the error Struct member 'info' causes a cycle in the struct layout.
I'm after struct like value type behaviour. I could simulate this using a class and a clone member function, but I don't see why I should need to.
How is this error true? Recursion could perhaps cause construction forever in some similar situations, but I can't think of any way that it could in this case. Below are examples that ought to be fine if the program would compile.
new info(1, 2);
new info(1, 2, null);
new info(1, 2, new info(3, 4));
edit:
The solution I used was to make "info" a class instead of a struct and giving it a member function to returned a copy that I used when passing it. In effect simulating the same behaviour as a struct but with a class.
I also created the following question while looking for an answer.
The reason why this creates a cycle is that
Nullable<T>
is itself astruct
. Because it refers back toinfo
you have a cycle in the layout (info
has a field ofNullable<info>
and it has a field ofinfo
) . It's essentially equivalent to the followingIt's not legal to have a struct that contains itself as a member. This is because a struct has fixed size, and it must be at least as large as the sum of the sizes of each of its members. Your type would have to have 8 bytes for the two floats, at least one byte to show whether or not
info
is null, plus the size of anotherinfo
. This gives the following inequality:This is obviously impossible as it would require your type to be infinitely large.
You have to use a reference type (i.e. class). You can make your class immutable and override
Equals
andGetHashCode
to give value-like behaviour, similar to theString
class.There isn't any way to achieve mutable value semantics of variable-sized items (semantically, I think what you're after is to have
MyInfo1 = MyInfo2
generate a new linked list which is detached from the one started by MyInfo2). One could replace theinfo?
with aninfo[]
(which would always either be null or else populated with a single-element array), or with a holder class that wraps an instance ofinfo
, but the semantics would probably not be what you're after. FollowingMyInfo1 = MyInfo2
, changes toMyInfo1.a
would not affectMyInfo2.a
, nor would changes toMyInfo1.c
affectMyInfo2.c
, but changes toMyInfo1.c[0].a
would affectMyInfo2.c[0].a
.It would be nice if a future version of .net could have some concept of "value references", so that copying a struct wouldn't simply copy all of its fields. There is some value to the fact that .net does not support all the intricacies of C++ copy constructors, but there would also be value in allowing storage locations of type 'struct' to have an identity which would be associated with the storage location rather than its content.
Given that .net does not presently support any such concept, however, if you want
info
to be mutable, you're going to have to either put up with mutable reference semantics (including protective cloning) or with weird and wacky struct-class-hybrid semantics. One suggestion I would have if performance is a concern would be to have an abstractInfoBase
class with descendantsMutableInfo
andImmutableInfo
, and with the following members:AsNewFullyMutable
-- Public instance -- Returns a newMutableInfo
object, with data copied from the original, callingAsNewFullyMutable
on any nested references.AsNewMutable
-- Public instance -- Returns a newMutableInfo
object, with data copied from the original, callingAsImmutable
on any nested references.AsNewImmutable
-- Protected instance -- Returns a newImmutableInfo
object, with data copied from the orignal, callingAsImmutable
(notAsNewImmutable
) on any nested references.AsImmutable
-- Public virtual -- For anImmutableInfo
, return itself; for aMutableInfo
, callAsNewImmutable
on itself.AsMutable
-- Public virtual -- For aMutableInfo
, return itself; for anImmutableInfo
, callAsNewMutable
on itself.When cloning an object, depending upon whether one expected that the object or its descendants would be cloned again before it had to be mutated, one would call either
AsImmutable
,AsNewFullyMutable
, orAsNewMutable
. In scenarios where one would expect an object to be repeatedly defensively cloned, the object would be replaced by an immutable instance which would then no longer have to be cloned until there was a desire to mutate it.The real problem is on this line:
Since this is a
struct
, C# needs to know the innerinfo
/s layout before it could produce outerinfo
's layout. And the innerinfo
includes an inner innerinfo
, which in turn includes an inner inner innerinfo
, and so on. The compiler cannot produce a layout because of this circular reference issue.Note:
info? c
is a shorthand forNullable<info>
which is itself astruct
.