您现在的位置:DotNet开发者 >> .NET开发 >> VC++.NET>> 内容正文

.NET 常见问答:完成器(Finalizer)、程序集名、方法信息等等

编辑:周郎   来源:本站整理   发布时间:2008-06-17 00:06:48
您正在看的VC.NET教程是:.NET 常见问答:完成器(Finalizer)、程序集名、方法信息等等。
原文出处:.NET Matters:Finalizers, Assembly Names, MethodInfo, and More  

源代码下载:NETMatters0405.exe (139 KB)

 


问题: 在我的类中何时需要实现一个完成器?我是否一定要实现完成器,或者只是在我控制着 非托管资源时才需要实现它?我是否一定要在我的完成器中实现 IDisposable 接口?反之又是如何的呢?

答案: 完成器只是在你控制了需要被清除的资源的才需要实现。举个例子,FileStream 控制了一个本地的文件句柄并且实现了一个完成器,这样也就保证了 FileStream 被垃圾回收器回收时能释放这个句柄。不幸的是,完成器给垃圾回收带来了一定意义上的负担,因此应仅在必要时才使用完成器。
  IDisposable 接口的实现表明你的类控制了需要被释放的资源,并且允许你的类用户决定是否要释放它们。因此,任何一个类实现了完成器就一定实现了 IDisposable 接口(如果垃圾回收器能自动释放资源,那么也应该允许开发者显式地调用某个方法来完成同样的工作)。但这并不是绝对的:并不是所有实现了IDisposable接口的类都要实现一个完成器。
   设想一下,我的托管类有一个FileStream类型的私有成员。FileStream 控制了一个非托管的资源并且实现了 IDisposable 接口和完成器。当对该实例不再有引用时,FileStream就变成无法访问的与可终结的了。对我的类而言,它没有理由在注册对象队列里等待终结,因为内嵌的FileStream的实例已经被注册了。另一方面,考虑到我的类应该为用户提供方法以立即释放它所控制的资源,无论这种方法是直接地还是间接的,因此我的类应当实现IDisposable接口。我的Dispose()实现很简单,它只是简单地调用了FileStream的Dispose()方法。切记:尽管如此,你也需要特别小心地释放共享资源(比如正被别的实例使用的资源)。如果你编写一个类从外部释放资源而使该资源可用,请确保你的文档在这个主题上是清晰明确的,这样其他人就知道是否正在移交他们给你的那些资源的控制权了。
   如果你的类不需要实现完成器,在你的Dispose()方法中应当调用GC.SuppressFinalize()方法,以确保系统不会去调用你的实例的完成器。这样你的实例同时也会从待终结对象集合中被删除,从而减轻了垃圾回收器在回收过程中的负担。贯穿于Microsoft .NET Framework中常见的实现模式是给Dispose()方法添加一个Boolean(逻辑)类型的参数。这个Boolean类型的参数指示这个类是否因IDisposable.Dispose()方法被调用或者完成器在运行而正在被释放(完成器与IDisposable.Dispose()都是委托到该方法上的)。如果确定它要被释放,GC.SuppressFinalize()就要被调用。如果是通过完成器被释放,就要避免再使用你的类中实现完成器的 托管成员,因为它们可能已经被终结了。
  Figure 1 提供了一些指导性的说明,帮助你在合适的时候在你的类中实现这些结构。

问题: 我希望把一些关系到应用程序性能的操作建立在计算机可用内存的基础上。如何才能最简单地从操作系统获取这些信息?

答案: 尽管我知道获取这类信息其他的一些方法,但当我发现WMI (Windows Management Instrumentation,Windows管理规范)时,我发现它才是完成这类工作最佳的方式。Win32_OperatingSystem类提供了关于操作系统事件的丰富信息,System.Management命名空间则提供了大量的类来访问WMI的数据。你可以使用 Figure 2 中的ManagementObjectSearcher类来查询Win32_OperatingSystem.TotalVisibleMemorySize的值。因为 ManagementObjectCollection (由ManagementObjectSearcher返回)没有公开访问集合内部元素的方法,因此我使用了一个foreach循环来枚举出其中的每一个成员。而且因为我只关心其中的一个值,所以我在第一次枚举完成后就停止了循环。
   注意TotalVisibleMemorySize返回的值可能并不是当前的物理内存总量,而是向操作系统报告可利用的内存量。你可以从Win32_OperatingSystem(Win32_OperatingSystem)这个WMI类中学会更多有用的东西。

问题: 我尝试着在未将程序集装载入我的AppDomain的情况下,获取该程序集的完全限定名。这可能做到吗?

答案: 绝对能!System.Reflection.AssemblyName类有一个static类型的方法GetAssemblyName(),这可以返回磁盘上一个程序集的名称AssemblyName。这个方法只是简单地打开这个程序集文件,而不会将它装载入AppDomain。下面的这段代码,就能在控制台上输出从命令行传入的路径参数对应程序集的完全限定名:
static void Main(string [] args)
{
if (args.Length > 0)
{
try
{
AssemblyName a = AssemblyName.GetAssemblyName(args[0]);
Console.WriteLine(a.Fullname);
}
catch(Exception exc)
{
Console.WriteLine(exc.Message);
}
}
}
注意:同样的技巧也可以用到本地托管的DLL或者EXE上。你可以在这些文件上挨个地试一试。如果没有异常被抛出,并且返回了一个有效的名字,那么这个文件就是托管的。当然,这种方式在所有的本地文件都触发异常的情况下也存在一些性能上的缺陷。当然还有另一种方法,它不依赖于反射,也不需要装载Portable Executable (PE)。而是通过分析DLL或者EXE的PE头中某个标识位是否被置位,由此确定它是否是 托管的。Managed Extensions for C++ requently Asked Questions中有实现这种方法的C++代码,等价的C#代码请参见 Figure 3。

问题: 在我的C#应用程序里有一大堆的foreach循环。当我检查编译器生成的MSIL 文件时(Microsoft intermediate language,Microsoft中间语言)时,我发现其中有的循环被嵌入了一个try/finally块,但我的源代码里并没有使用啊?它们为什么会在这里出现?

答案: 问得好!记住foreach循环是用来枚举那些集合型的数据的。它是通过获取一个枚举器后,使用枚举器的MoveNext操作来实现遍历,并在Current数据属性中返回集合中的当前项。这个枚举器本身则是通过调用该集合对象的GetEnumerator()方法获得的。因为枚举器可能实现了IDisposable接口,所以C#的编译器需要在枚举完成后将其释放。为此,编译器会将这个循环放入一个try块,然后试着在finally块中释放这个枚举器。如果利用GetEnumerator ()方法返回的枚举器实现了IDisposable接口,编译器就会生成一个类似下面这样的finally块:
((IDisposable)enumerator



【免责声明】
本站刊载此文仅为提供更多信息,不代表同意其说法,也不构成任何建议。有任何异议,请联络:web@zhoulang.net
相关文章
  • 很抱歉!还没有相关内容
站内搜索
小提示:苦寻不到想要的?不防搜索一下!
更多>>
DotNet最新文章
更多>>
网站运营最新文章