C# 食人族

C# 食人族,c#,.net,C#,.net,一段时间以来,我一直在想为什么允许编译一些“食人族”类 在我继续之前,也许我应该解释一下我称之为“食人族”的课程。我不确定这个词是我刚刚发明的,还是已经存在了一段时间,或者我是否正确地使用了它,但现在这并不重要 我基本上把食人族称为一个自我消费的类。换句话说,一个类的接口声明了它自己类型的成员。例如: class Foo { public Foo SomeFoo; } 如上所示,类Foo有一个Foo类型的成员(本身) 现在,我第一次看到这个(很久以前)我并不认为它会编译,但令我惊讶的

一段时间以来,我一直在想为什么允许编译一些“食人族”类

在我继续之前,也许我应该解释一下我称之为“食人族”的课程。我不确定这个词是我刚刚发明的,还是已经存在了一段时间,或者我是否正确地使用了它,但现在这并不重要

我基本上把食人族称为一个自我消费的类。换句话说,一个类的接口声明了它自己类型的成员。例如:

class Foo
{
    public Foo SomeFoo; 
}
如上所示,类Foo有一个Foo类型的成员(本身)

现在,我第一次看到这个(很久以前)我并不认为它会编译,但令我惊讶的是它确实编译了。我之所以没有编译这个东西,是因为对我来说,这是一种递归噩梦

为了使事情更加复杂,我决定尝试同样的方法,但将类设为结构,例如:

struct Foo
{
    public Foo SomeFoo; 
}
不幸的是,这不会编译,而是会出现错误:结构成员“Foo.SomeFoo”类型为“Foo”会导致结构布局中出现循环

对我来说,编译错误比没有错误更有意义,但我确信对这种行为有一个合乎逻辑的解释,所以我想知道你们中是否有人可以解释这种行为


谢谢。

那么您是否将单例实现称为canibal类

public class ShopSettings
{
  public static ShopSettings Instance
  {
    get
    {
      if (_Instance == null)
      {
        _Instance = new ShopSettings();
      }

      return _Instance;
    }
  }
}

不能这样设计结构的原因是,当结构被分配一些默认值时,必须对其进行初始化。所以,当你有一个像你描述的结构
Foo
,并且你创建了一个
Foo

Foo x; // x = default(Foo)
它调用
Foo
的默认构造函数。但是,默认构造函数必须为
Foo.SomeFoo
字段提供默认值

那它是怎么发现的?它必须调用
default(Foo)
。为了构造
Foo
,必须为
Foo.SomeFoo
字段提供默认值。。。正如你所猜测的,这是一场递归的噩梦

因为您可以创建一个类的空引用,并且避免立即实际创建该类的实例,所以没有问题;您可以调用
newfoo()
Foo。SomeFoo
将为空。不需要递归

附录:当你思考这个问题时,如果你仍然感到困惑,我认为其他答案有另一种很好的方式来考虑它(相同的基本问题,不同的方法)——当程序为
Foo
分配内存时,它应该如何做?当
Foo
包含一些内容,然后是另一个完整的
Foo
时,
sizeof(Foo)
是什么?你不能那样做

相反,如果它是一个类,它只需要为一个
Foo
的引用分配几个字节,而不是一个实际的
Foo
,并且没有问题

class Foo
{
    public Foo SomeFoo; 
}
本例中的SomeFoo只是一个链接,不会导致递归创建问题
正因为如此,canibal类可以存在。

区别在于Foo是一种引用类型。 类内存布局将有一个指向Foo实例的指针,这很好

在这种结构的情况下,您基本上有一个内循环内存布局,无法工作


现在,如果您的类尝试在其自己的构造函数中实例化一个Foo,那么您将遇到堆栈溢出问题。

正如他们所说的,它是一个valuetype,因此它不能包含自己,因为它无法工作(想想看)

但是,您可以执行以下操作:

unsafe struct Foo
{
  public Foo* SomeFoo;
}

它们被称为递归类型,非常常见。例如,树通常是根据引用其他节点的“节点”来实现的。这并没有什么自相矛盾的地方,只要它们相互提及,但不相互牵制

一种方法是,一个整数的平方总是另一个整数。这并不奇怪,它只是两个整数之间的引用或关系。但不能将包含自身完整副本的字符串作为子字符串。这就是区别。

好的,我明白了

因此,为了阐明这一点,如果我理解正确的话,编译器并不关心成员是否为Foo、Bar或其他类型。编译器只需要知道它需要为该成员变量分配的字节数

因此,如果将其编译为32位操作系统,那么我假设编译器在技术上正在将Foo类型的声明更改为如下内容:

class Foo
{
    public Int32 SomeFoo; 
}
编译器实际上看到的不是“Foo”,而是32位(某种程度上)


谢谢。

要了解为什么允许这样做,以及它如何有用,经典的链表实现就是一个很好的例子,请查看本教程

您可以看到,它们定义了一个节点类,如下所示:

public class LinkedList
{

    Node firstNode;

    public class Node
    {

        Node previous;
        Node next;
        int value;
    }
}
每个节点都指向上一个和下一个节点,因此将其视为指向另一个对象的链接是一个良好的开端


如果你以前没有做过链表,我强烈推荐它,因为它是一个很好的例子。

总结一下:class=reference-type,struct=value-type。无法布局包含自身的类型的内存映射。但是,引用类型将只包含内存引用,而不包含实际的数据结构。因此,不需要知道类型在编译时是如何构造的。-1,不,你的实例是静态的,这样可以完全避免这个问题。我刚刚发明了一种包含自身副本的东西。。。我想我会称之为分形啊,但是分形并不包含它们自身的完整副本。取而代之的是,分形的一部分可以在某种映射下与整体相对应,直到某些epsilon.LOL同类。这让我笑了:)从来没有听说过它叫食人族,通常是自我参照,但我更喜欢食人族。最终,记忆方面,是的。这是正确的