Delphi接口和C#接口在内存使用方面有什么区别(清理等)?

6

我是一名Delphi程序员,现在正在尝试使用C#进行编程。请问C#中的接口和Delphi中的接口是否相同,即在接口离开作用域时,无需担心释放其内存?


罗纳尔多,你是在特别询问接口还是你指的是引用? - H H
我在询问接口相关的问题 - 由于Delphi处理接口的方式,我即将构建一些东西,并希望使用接口,但缺乏对其背后发生的事情的知识。 - ronaldosantana
那部分现在应该很清楚了 (-: - H H
5个回答

8
在此领域中Delphi和.NET的关键区别,具体涉及接口方面,是清理过程的非确定性特性。在Delphi中,所有接口使用都遵循COM模型,也就是说,它是引用计数的。如果类实现了引用计数的生命周期管理模型,则当引用计数降至零时,对象实例将在那一点被销毁。
注意:生命周期管理是类实现的一个函数。要最清楚地看到这一点,请查看TInterfacedObject中IUnknown.Release的实现。
function TInterfacedObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

如果实现Release没有调用Destroy,那么当引用计数降至零时,该对象将不会被销毁,并且仍然必须通过某些对象引用显式释放。在Delphi中,可以使用这种方法创建实现接口但不受自动引用计数生命周期管理的对象(尽管无法避免由编译器注入的引用计数代码,即对AddRefRelease的调用)。
首先,在.NET中没有所谓的引用计数。垃圾收集器以一种更为复杂的方式工作,其详细信息与本讨论不直接相关。
关键区别在于,当一个对象不再被使用(不管是通过简单的引用计数还是其他方式判断)时,在.NET中不是它被销毁的时间点。
事实上,在您的进程生命周期中,未使用的对象可能会积累到更晚的时间点——特别是在具有少量空闲周期的计算密集型应用程序中。在.NET中,您无法确定这些未使用的对象何时会被释放。
一些人认为这是“好事”,尽管它混淆了在对象被销毁时清理被锁定或拥有的资源的通常做法,因为您通常需要更紧急地释放那些被锁定/拥有的资源,而不是等待垃圾收集器。这就是.NET中IDisposable的作用,其详细信息也与本讨论不直接相关,可以在您方便时进一步研究。

非常感谢你的详细解释 - 你和Henk提供的答案对我帮助很大。 - ronaldosantana

7

对于程序员来说,它们看起来是一样的。使用和遗忘。

内部工作方式不同,在.NET中,接口是(仅仅是)被垃圾回收器扫描的另一个引用。

在Delphi中,接口是一种特殊类型的引用,它们是引用计数的(一种不同的内存管理技术)。

.NET/Delphi的主要区别在于,在.NET中,所有引用(接口、对象和数组)都是由垃圾回收器进行回收的。


1
谢谢你的解释。我可以这么说吗,如果我让我的对象实现 IDisposable 接口并使用类似于 "using (MyClass myobj = new MyClass()) {}" 的方式,我是否可以实现类似 Delphi (超出作用域后立即完成)的效果,而无需等待垃圾回收? - ronaldosantana
@Ronaldo:是的,有点类似。但通常情况下你不应该这样做。IDisposable 用于管理资源(FileHandles),而不是内存。你无法控制对象的生命周期,但也不必担心它。 - H H

2

我认为它并不会在作用域外被释放,而是在垃圾回收发生时(即当它们不再被使用时)进行释放。


1
实际上是正确的,但很难理解:Delphi 的行为是什么,.NET 的行为又是什么? - H H

0

C# 中的所有内容都是垃圾回收的,因此您不需要手动释放它们。

但是有一些显著的区别。Delphi 接口是引用计数的,而 C# 接口是垃圾回收的。C# 支持多接口继承。


1
Delphi同样支持多重接口继承,没有任何区别。 - H H
1
可以毫不夸张地说,C#和Delphi都支持多接口实现,这是正确的吗? “多接口继承”可能会暗示一个接口“ A”可以从多个接口“ B”和“ C” 继承,但无论是Delphi还是C#都不可能。当然,如果一个类实现了多个接口,则任何派生/后代类都将“继承”这些多个接口,但在我看来,这只是实现继承和实现多个接口的副作用,并不构成多接口继承的“特性”。 - Deltics
@Deltics:请注意,interface IFoo : IBar, IDisposable 是可以的,但是当你反射它回来时,实现IFoo接口的类可能会显示出三个不相关的接口。 - H H
@Henk:感谢你提供的链接。对我来说,这似乎是毫无意义的语法糖...从多个接口"继承"在语义上和功能上等同于要求实现多个接口(如果实现了一个指定的“多派生”接口)。也就是说,这是一种实现接口契约的有用方式,但它并不是通常在面向对象中所指的"继承",而且你的反射观察似乎证实了这一点——它是看起来像继承的语法糖,但实际上并不是。至少,在我看来是这样。 - Deltics
@Deltics:我不同意,这是有道理的。就像 IFoo f = ...; Ibar b = f; 每个 IFoo 都是一个 IBar。替换原则在起作用。 - H H
好的,你提供的SO问题的被接受答案并没有说:"接口派生关系根本不意味着这一点"..."接口继承并不意味着“这是那种东西”,而是“每个实现都需要实现那个”。"<耸肩> - Deltics

0

CLR 处理内存管理。垃圾回收是 CLR 中的内在服务。

您不需要将变量或字段置空以便通过垃圾回收来收集对象。

您不需要实现或使用 IDisposable 来清理内存。Dispose 方法对于释放内存中的托管对象没有任何作用。Dispose 方法应该用于释放“非托管”资源,包括数据库连接、位图或任何您可能持有的非托管结构。

如果您谈论的是视觉组件(如窗体、对话框等)中的接口,则会变得更加混乱。在 WinForms 中,我相信当一个接口(可视化)不再可见时,它将被清理并进行垃圾回收。当需要时,窗体将重新创建。在 WPF 中,Windows 和 Pages 不会立即销毁。它们会被缓存到应用程序的生命周期结束,除非应用程序开发人员明确清理它们。这提高了 WPF 应用程序的性能,但也增加了您必须关注应用程序资源的额外负担。

垃圾回收是一个完全独立的主题。从物理内存中实际释放托管对象是在此处完成的,完全由垃圾回收服务控制,您通常不必担心其工作原理。

干杯


对于“you do not need to null out ...”这句话,您不需要将其置为null。变量为True,字段危险地为False。 - H H
对于其余部分,这缺乏任何 Delphi 视角。 - H H
当类超出范围时,字段将被清理。为什么需要将字段置空?如果你的类持有非托管资源,你需要在Dispose方法中清理它们。将字段置空并不一定清理非托管资源。这个问题也涉及到C#中的内存管理。我错过了Delphi接口和C#接口之间的明确链接,但并没有感觉到我的贡献受到了损害。 - Dave White
您可能需要将持有对象引用的字段置空,以使对象可被收集。我现在看到了您的另一种解释,但那毫无意义 - 没有任何语言会让您清理变量/字段。接口也不包含字段。 - H H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接