【问题标题】:I want to allocate an object on the stack with C#我想用 C# 在堆栈上分配一个对象
【发布时间】:2011-05-05 09:23:18
【问题描述】:

假设我有这个 C# 类:

public class HttpContextEx
{
    public HttpContext context = null;
    public HttpRequest req = null;
    public HttpResponse res = null;
}

如何在函数内声明它的对象,该对象将分配在堆栈上而不是堆上?
换句话说,我想避免为此使用“新”关键字。此代码错误

HttpContextEx ctx = new HttpContextEx(); // << allocates on the heap!

我完全了解堆栈/堆是什么,并且听说过美妙的 C# GC,但我坚持在堆栈上分配这个仅为方便起见的小对象。

这种态度来自C++(我的主要工具),所以我不能忽视这一点,我的意思是它真的破坏了我在这里的乐趣(:

【问题讨论】:

  • 你也得用 C# 来思考
  • @Poni:那么您可能应该只编写 C# 而不是尝试在 C# 中编写 C++。我想我并不太明白问题出在哪里,但 C#/.NET 之所以这样设计是有原因的。
  • 创建HttpContextEx 的意义何在?现有的 HttpContext 类已经提供了 RequestResponse 属性。
  • @John Rasch 在 12 多个函数之间传递相同的六个指针时,我很懒惰,而我可以简单地传递一个不错的对象。
  • 为了简化——我不知道会出现这样的答案,如果我知道这些是我不会问的答案,我会吗?!?另外,aqwert 的答案就是答案,没有所有的“bla bla”。

标签: c# memory-management


【解决方案1】:

如果您使用struct 将其更改为值类型并在方法体中创建一个新实例,它将在堆栈上创建它。然而,成员,因为它们是引用类型,仍然会在堆上。无论是值还是引用类型的语言仍然需要 new 运算符,但您可以使用 var 来消除类型名称的双重使用

var ctx = new HttpContextEx(); 

否则,就照原样使用 C#,因为 GC 做得很好。

【讨论】:

  • "但是你可以使用 var 来消除类型名的双重使用"
  • 这样我就可以链接这个great article by Eric Lippert - 将值类型放在堆栈上是所有当前运行时的实现细节,但它们也可能存储在堆上。 (part 2)
【解决方案2】:

您不能(也不应该)这样做。即使您使用struct(将被放入堆栈),您也必须对包含的类使用new 运算符。严肃地说,如果你改用另一种语言,也要改变你的态度。

【讨论】:

  • 使用 ValueType 派生来获取堆栈分配只是当前 CLR 实现的产物。没有要求在规范中如此。
  • @Poni:虽然结构很有用,但它们应该是不可变的,并且应该很小(例如 16 字节或更少),因为每次将它们传递给子例程时,整个内存块都代表对象被复制,除非您想在所有函数参数中使用ref。在 5 年以上或 C# 开发中,我还没有找到需要担心 .Net 堆栈/堆速度的案例。简而言之,我只是声明类并让 .Net 担心它,而我专注于应用程序。
  • 如果它能让你的船浮起来,好吧 - 我只是希望(对你)它也能浮在你雇主的船上。
  • 哇,波妮。您愿意为了避免 C# 中的最佳实践而降低性能吗?您知道,在堆栈上分配甚至不是您的习惯,而是编译器或运行时的习惯。您只需声明一个变量,编译器/运行时决定将它放在哪里。所以你的意思是......你想让 .NET 运行时继承你的 C++ 编译器的习惯吗?
  • @Poni:可悲的是,您在这里的最后三个 cmets 表明 C++ 对您的心灵和抽象思考软件的能力造成了不可逆转的损害。你给出的堆栈与堆的定义让我的大脑爆炸了。你对底层实现的假设远远太多了。
【解决方案3】:

您必须来自 C++。 .Net 中的情况并非如此。

所有引用类型都分配在托管堆上,由 GC 跟踪它们。对于快速退出的函数范围内的对象引用,分配的对象很可能仅保留在托管堆的第 0 代中,这会导致非常有效的内存收集。托管堆被调整为处理这样​​的短期对象。它甚至没有你可能习惯的 C++ 堆的分配策略。

这就是 CLR 的工作方式。如果您想以其他方式工作,请尝试使用非托管运行时。

【讨论】:

    【解决方案4】:

    在 .NET 中,类是引用类型,结构是值类型。

    如果你真的想要在栈上分配的类型,你可以做一个结构。然而,有几个理由不这样做:

    • 正确实施结构更复杂。在你有充分的理由创建结构之前,你应该坚持使用类。
    • 结构适用于表示单个单元的类型,而您的类型只是三个独立单元的容器。
    • 结构不应大于 16 字节才能有效工作。在 32 位系统上,您会低于该限制,但在 64 位系统上,结构要大。
    • 结构应该是不可变的,才能作为值类型正常工作,而您的类型不是。

    最后,在堆上分配小对象本身并没有什么坏处。内存管理器实际上是专门为有效处理小型短期对象而设计的。

    下面是一个代码示例,如果它是一个结构体则不起作用,但如果它是一个类则可以正常工作:

    public void SetContext(HttpContextEx ex) {
      ex.context = HttpContext.Current;
      ex.req = ex.context.Request;
      ex.res = es.context.Response;
    }
    
    HttpContextEx ctx = new HttpCoontextEx();
    SetContext(ctx);
    

    【讨论】:

    • 关于代码;我明白那个。我也明白,通过输入“ref”关键字,我已经准备好了。对吧?
    • @Poni:您可以使用 ref 关键字来使该代码工作,但还有其他类似的情况,即未正确实现的结构无法工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 2020-08-24
    • 1970-01-01
    • 2014-05-30
    • 2020-03-30
    • 2018-09-09
    相关资源
    最近更新 更多