问题: 在我的类中何时需要实现一个完成器?我是否一定要实现完成器或者只是在我控制着 非托管资源时才需要实现它?我是否一定要在我的完成器中实现 IDisposable 接口?反之又是如何的呢? 答案: 完成器只是在你控制了需要被清除的资源的才需要实现举个例子FileStream 控制了一个本地的文件句柄并且实现了一个完成器这样也就保证了 FileStream 被垃圾回收器回收时能释放这个句柄不幸的是完成器给垃圾回收带来了一定意义上的负担因此应仅在必要时才使用完成器 IDisposable 接口的实现表明你的类控制了需要被释放的资源并且允许你的类用户决定是否要释放它们因此任何一个类实现了完成器就一定实现了 IDisposable 接口(如果垃圾回收器能自动释放资源那么也应该允许开发者显式地调用某个方法来完成同样的工作)但这并不是绝对的并不是所有实现了IDisposable接口的类都要实现一个完成器 设想一下我的托管类有一个FileStream类型的私有成员FileStream 控制了一个非托管的资源并且实现了 IDisposable 接口和完成器当对该实例不再有引用时FileStream就变成无法访问的与可终结的了对我的类而言它没有理由在注册对象队列里等待终结因为内嵌的FileStream的实例已经被注册了另一方面考虑到我的类应该为用户提供方法以立即释放它所控制的资源无论这种方法是直接地还是间接的因此我的类应当实现IDisposable接口我的Dispose()实现很简单它只是简单地调用了FileStream的Dispose()方法切记尽管如此你也需要特别小心地释放共享资源(比如正被别的实例使用的资源)如果你编写一个类从外部释放资源而使该资源可用请确保你的文档在这个主题上是清晰明确的这样其他人就知道是否正在移交他们给你的那些资源的控制权了 如果你的类不需要实现完成器在你的Dispose()方法中应当调用GCSuppressFinalize()方法以确保系统不会去调用你的实例的完成器这样你的实例同时也会从待终结对象集合中被删除从而减轻了垃圾回收器在回收过程中的负担贯穿于Microsoft NET Framework中常见的实现模式是给Dispose()方法添加一个Boolean(逻辑)类型的参数这个Boolean类型的参数指示这个类是否因IDisposableDispose()方法被调用或者完成器在运行而正在被释放(完成器与IDisposableDispose()都是委托到该方法上的)如果确定它要被释放GCSuppressFinalize()就要被调用如果是通过完成器被释放就要避免再使用你的类中实现完成器的 托管成员因为它们可能已经被终结了 Figure 提供了一些指导性的说明帮助你在合适的时候在你的类中实现这些结构 问题: 我希望把一些关系到应用程序性能的操作建立在计算机可用内存的基础上如何才能最简单地从操作系统获取这些信息? 答案: 尽管我知道获取这类信息其他的一些方法但当我发现WMI (Windows Management InstrumentationWindows管理规范)时我发现它才是完成这类工作最佳的方式Win_OperatingSystem类提供了关于操作系统事件的丰富信息SystemManagement命名空间则提供了大量的类来访问WMI的数据你可以使用 Figure 中的ManagementObjectSearcher类来查询Win_OperatingSystemTotalVisibleMemorySize的值因为 ManagementObjectCollection (由ManagementObjectSearcher返回)没有公开访问集合内部元素的方法因此我使用了一个foreach循环来枚举出其中的每一个成员而且因为我只关心其中的一个值所以我在第一次枚举完成后就停止了循环 注意TotalVisibleMemorySize返回的值可能并不是当前的物理内存总量而是向操作系统报告可利用的内存量你可以从Win_OperatingSystem(Win_OperatingSystem)这个WMI类中学会更多有用的东西 问题: 我尝试着在未将程序集装载入我的AppDomain的情况下获取该程序集的完全限定名这可能做到吗? 答案: 绝对能!SystemReflectionAssemblyName类有一个static类型的方法GetAssemblyName()这可以返回磁盘上一个程序集的名称AssemblyName这个方法只是简单地打开这个程序集文件而不会将它装载入AppDomain下面的这段代码就能在控制台上输出从命令行传入的路径参数对应程序集的完全限定名 static void Main(string [] args) { if (argsLength > ) { try { AssemblyName a = AssemblyNameGetAssemblyName(args[]); ConsoleWriteLine(aFullname); } catch(Exception exc) { ConsoleWriteLine(excMessage); } } } 注意同样的技巧也可以用到本地托管的DLL或者EXE上你可以在这些文件上挨个地试一试如果没有异常被抛出并且返回了一个有效的名字那么这个文件就是托管的当然这种方式在所有的本地文件都触发异常的情况下也存在一些性能上的缺陷当然还有另一种方法它不依赖于反射也不需要装载Portable Executable (PE)而是通过分析DLL或者EXE的PE头中某个标识位是否被置位由此确定它是否是 托管的Managed Extensions for C++ requently Asked Questions中有实现这种方法的C++代码等价的C#代码请参见 Figure 问题: 在我的C#应用程序里有一大堆的foreach循环当我检查编译器生成的MSIL 文件时(Microsoft intermediate languageMicrosoft中间语言)时我发现其中有的循环被嵌入了一个try/finally块但我的源代码里并没有使用啊?它们为什么会在这里出现? 答案: 问得好!记住foreach循环是用来枚举那些集合型的数据的它是通过获取一个枚举器后使用枚举器的MoveNext操作来实现遍历并在Current数据属性中返回集合中的当前项这个枚举器本身则是通过调用该集合对象的GetEnumerator()方法获得的因为枚举器可能实现了IDisposable接口所以C#的编译器需要在枚举完成后将其释放为此编译器会将这个循环放入一个try块然后试着在finally块中释放这个枚举器如果利用GetEnumerator ()方法返回的枚举器实现了IDisposable接口编译器就会生成一个类似下面这样的finally块 ((IDisposable)enumerator)Dispose(); 如果枚举器没有实现IDisposable接口那么编译器仍然不得不试着释放这个对象因为不能确定枚举器的派生类型(从这个函数返回的)没有实现IDisposable接口于是编译器又会生成类似这样的一个finally块IDisposable disposable = enumerator as IDisposable; if (disposable != null) disposableDispose(); 因为大多数的可枚举类都有一个GetEnumerator()方法以返回IEnumerator接口(没有实现IDisposable接口)所以上述的代码只是编译器生成结果的一般情形在少数情况下当返回的类型没有实现IDisposable接口而且是密封的(指不能从其再派生新的类)时编译器就不需要实现一个try/finally块了——因为没有什么需要释放的了 问题: 我希望能枚举出一个对象层中的所有对象但是我不知道如何才能避免很有可能产生的循环引用NET Framework对此提供了什么帮助没有? 答案: 遍历一个对象层中的所有对象并以某种形式将它们序列化非常类似于完成一个BinaryFormatter那样的远程格式化程序对此NET Framework也提供了一些很有用的类来完成这类工作SystemRuntimeSerialization命名空间中的ObjectIDGenerator类可以看作是一张专门化的表用于跟蹤对象并赋与每个对象一个唯一的标识符你可以通过这个类查询对象的ID——它既可以向你提供继存对象的ID也可以为一个未出现的对象生成一个新的ID正因它能防止你两次遍历同一个对象所以你可以很容易地避免循环引用了 |