在不存在的结构布局中循环

时间:2012-02-15 15:33:34

标签: c# constructor struct member cyclic-reference

这是我的部分代码的简化版本:

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;
    }
}

问题是错误Struct member 'info' causes a cycle in the struct layout.我在结构类似于值类型行为之后。我可以使用类和克隆成员函数来模拟这个,但我不明白为什么我需要。

这个错误是怎么回事?在某些类似的情况下,递归可能会永远导致构造,但在这种情况下我无法想到它的任何方式。以下是程序编译时应该没问题的例子。

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

编辑:

我使用的解决方案是使“info”成为一个类而不是一个struct,并给它一个成员函数来返回我在传递它时使用的副本。实际上模拟与结构相同的行为但是具有类。

我在寻找答案时也提出了以下问题。

Value type class definition in C#?

5 个答案:

答案 0 :(得分:28)

拥有一个包含自身作为成员的结构是不合法的。这是因为结构具有固定大小,并且它必须至少与其每个成员的大小总和一样大。您的类型必须为两个浮点数提供8个字节,至少一个字节用于显示info是否为空,加上另一个info的大小。这给出了以下不等式:

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

这显然是不可能的,因为它会要求你的类型无限大。

您必须使用引用类型(即类)。您可以使您的类不可变,并覆盖EqualsGetHashCode以提供类似于String类的类似行为。

答案 1 :(得分:11)

这会产生一个循环的原因是Nullable<T>本身就是struct。由于它引用info,因此您在布局中有一个周期(info的字段为Nullable<info>且字段为info)。它基本上等同于以下

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

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

答案 2 :(得分:4)

真正的问题在于这一行:

public info? c;

由于这是struct,因此C#需要知道内部info / s布局才能生成外部info的布局。内部info包括内部info,内部info又包括内部内部info? c,依此类推。由于此循环引用问题,编译器无法生成布局。

注意:Nullable<info>struct的简写,{{1}}本身就是{{1}}。

答案 3 :(得分:2)

没有任何方法可以实现可变大小项的可变值语义(从语义上讲,我认为你所要做的是让MyInfo1 = MyInfo2生成一个新的链表,该列表与启动的链表分离。 MyInfo2)。可以用info?替换info[](它总是为null或者用单元素数组填充),或者用包含info实例的holder类替换MyInfo1 = MyInfo2,但语义可能不是你想要的。关注MyInfo1.a,对MyInfo2.a的更改不会影响MyInfo1.cMyInfo2.c的更改也不会影响MyInfo1.c[0].a,但更改为MyInfo2.c[0].a会影响info {1}}。

如果.net的未来版本可能具有“值引用”的概念,那将是很好的,因此复制结构不会简单地复制其所有字段。 .net不支持C ++拷贝构造函数的所有复杂性这一事实是有价值的,但是允许类型为'struct'的存储位置具有与存储位置相关联的标识而不是存储位置也是有价值的。它的内容。

鉴于.net目前不支持任何此类概念,但是,如果您希望InfoBase是可变的,那么您将不得不忍受可变引用语义(包括保护性克隆)或者奇怪而古怪的struct-class-hybrid语义。如果表现令人担忧,我会提出一个建议,就是要有一个带有后代MutableInfoImmutableInfo的抽象AsNewFullyMutable课程,以及以下成员:

  1. MutableInfo - 公共实例 - 返回一个新的AsNewFullyMutable对象,其中包含从原始数据复制的数据,在任何嵌套引用上调用AsNewMutable

    < / LI>
  2. MutableInfo - 公共实例 - 返回一个新的AsImmutable对象,其中包含从原始数据复制的数据,在任何嵌套引用上调用AsNewImmutable

    < / LI>
  3. ImmutableInfo - 受保护的实例 - 返回一个新的AsImmutable对象,其中包含从orignal复制的数据,在任何地方调用AsNewImmutable(不是AsImmutable)嵌套引用。

  4. ImmutableInfo - 公共虚拟 - 对于MutableInfo,返回自己;对于AsNewImmutable,请自行调用AsMutable

  5. MutableInfo - 公共虚拟 - 对于ImmutableInfo,返回自己;对于AsNewMutable,请自行调用AsImmutable

  6. 克隆对象时,根据是否预期在必须进行变异之前再次克隆对象或其后代,可以调用AsNewFullyMutableAsNewMutable或{{1 }}。在人们期望一个对象被反复进行防御性克隆的情况下,该对象将被一个不可变的实例取代,这个实例在不希望被克隆之前不再需要克隆。

答案 4 :(得分:1)

免责声明:这可能无法实现“类似结构的值类型行为”的目标。

一种解决方案是使用一个由一项组成的数组从本质上获得对递归引用结构的引用。使我的方法适应您的代码看起来像这样。

public struct info
{
    public float a, b;
    public info? c
    {
        get
        {
            return cArray[nextIndex];
        }
        set
        {
            steps[nextIndex] = value;
        }
    }
    private info?[] cArray;

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