22

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.

Value type class definition in C#?

4

5 回答 5

35

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

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

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

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

于 2012-02-15T15:36:44.590 回答
13

The reason why this creates a cycle is that Nullable<T> is itself a struct. Because it refers back to info you have a cycle in the layout (info has a field of Nullable<info> and it has a field of info) . It's essentially equivalent to the following

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

struct info { 
  public float a, b;
  public MyNullable<info> next;
}
于 2012-02-15T15:35:33.213 回答
5

真正的问题在于这一行:

public info? c;

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

注意:info? c是一个简写,Nullable<info>它本身就是一个struct.

于 2012-02-15T15:36:23.057 回答
2

没有任何方法可以实现可变大小项目的可变值语义(从语义上讲,我认为您所追求的是MyInfo1 = MyInfo2生成一个新的链表,该链表与由 MyInfo2 启动的链表分离)。可以info?用 an info[](始终为 null 或用单元素数组填充)或包装 的实例的持有者类替换info,但语义可能不是您所追求的。以下MyInfo1 = MyInfo2,更改MyInfo1.a不会影响MyInfo2.a,更改也不会MyInfo1.c影响MyInfo2.c,但更改MyInfo1.c[0].a会影响MyInfo2.c[0].a

如果 .net 的未来版本可以有一些“值引用”的概念,那就太好了,这样复制一个结构就不会简单地复制它的所有字段。.net 不支持 C++ 复制构造函数的所有复杂性这一事实有一定的价值,但允许“struct”类型的存储位置具有与存储位置相关联的身份而不是与存储位置相关联也很有价值。它的内容。

鉴于 .net 目前不支持任何此类概念,但是,如果您想info成为可变的,您将不得不忍受可变引用语义(包括保护性克隆)或奇怪而古怪的 struct-class-hybrid语义。如果性能是一个问题,我会有一个建议是有一个InfoBase带有后代的抽象类MutableInfoImmutableInfo,并具有以下成员:

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

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

  3. AsNewImmutable-- 受保护的实例 -- 返回一个新ImmutableInfo对象,其中包含从原始对象复制的数据,在任何嵌套引用上调用AsImmutable(not )。AsNewImmutable

  4. AsImmutable-- 公共虚拟 -- 对于 an ImmutableInfo,返回自身;对于 a MutableInfo,调用AsNewImmutable自身。

  5. AsMutable-- 公共虚拟 -- 对于 a MutableInfo,返回自身;对于ImmutableInfo, 调用AsNewMutable自身。

当克隆一个对象时,根据人们是否期望该对象或其后代在它必须被突变之前再次被克隆,人们可以调用AsImmutable,AsNewFullyMutableAsNewMutable. 在人们期望一个对象被反复防御性克隆的情况下,该对象将被一个不可变的实例替换,该实例将不再需要克隆,直到有对其进行变异的愿望。

于 2012-02-15T17:28:21.567 回答
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;
    }
}
于 2019-07-15T14:43:43.170 回答