3.5 元件版本如何起作用?
每个元件由一个称为兼容性版本的版本号。同样,对元件的引用 (从另一个元件) 包括被引用元件的名称和版本。
版本号有四个数字部分 (例如 5.5.2.33)。前两部分不相同的元件被视为不兼容的。如果前两部分相同,但第三部分不同,元件被认为“可能兼容”。如果仅仅第四部分不同,则元件被视为是兼容的。然而,这只是默认的指导方针?是 版本策略决定施用这些规则的范围。版本策略可以在应用程序配置文件中指定。
记住:版本控制仅仅针对于共享元件,而不对私有元件。
4. 应用程序域
4.1 什么是应用程序域?
应用程序域 (AppDomain) 可以被看作一个轻型的进程。在一个 Win32 进程中可以存在多个 AppDomain。AppDomain 的主要目的是将应用程序和其它应用程序隔离开来。
通过使用独立的地址空间,Win32 进程提供隔离性。这种方法很有效,但开销很大并且伸缩性不好。.NET 运行库通过控制对内存的是用来施加 AppDomain 隔离?AppDomain 中的所有内存是由 .NET 运行库来管理的,所以运行库可以确保 AppDomain 之间不能访问彼此的内存。
4.2 如何创建 AppDomain?
AppDomains 通常有宿主创建。宿主包括 Windows Shell、ASP+ 和 IE。当你从命令行运行一个 .NET 应用程序时,宿主是 Shell。Shell 为每个应用程序创建一个新的 AppDomain。
AppDomains 也可以由 .NET 应用程序来显式创建。这里是一个创建 AppDomain 的一个 C# 例子,它创建对象的一个实例,并随后执行对象的一个方法:
using System;
using System.Runtime.Remoting;
public class CAppDomainInfo : MarshalByRefObject
{
public string GetAppDomainInfo()
{
return "AppDomain = " + AppDomain.CurrentDomain.FriendlyName;
}
}
public class App
{
public static int Main()
{
AppDomain ad = AppDomain.CreateDomain( "Andy's new domain", null, null );
ObjectHandle oh = ad.CreateInstance( "appdomaintest.exe", "CAppDomainInfo" );
CAppDomainInfo adInfo = (CAppDomainInfo)(oh.Unwrap());
string info = adInfo.GetAppDomainInfo();
Console.WriteLine( "AppDomain info: " + info );
return 0;
}
}
4.3 我能编写自己的 .NET 宿主吗?
能。关于怎样来做的例子,看看 Jason Whittington 和 Don Box 开发的 dm.net moniker 的源代码 (http://staff.develop.com/jasonw/clr/readme.htm)。在 .NET SDK 中也有一个叫作 CorHost 的代码示例。
5. 垃圾收集
5.1 什么是垃圾收集?
垃圾收集是一个系统,运行库组件通过它来管理对象的生存周期和它们占用的堆内存。对 .NET 而言它并不是一个新概念?Java 和许多其它语言/运行库使用垃圾收集已经有一段时间了。
5.2 对对象的最后一个引用撤销后,它并不一定立即被破坏,对吗?
是的。垃圾收集器并不提供销毁对象并是放其内存的时间保证。
关于 C# 中隐含的非确定化对象析构,Chris Sells 有一个令人感兴趣的线索:http://discuss.develop.com/archives/wa.exe?A2=ind0007&L=DOTNET&P=R24819
2000 年 10 月,Microsoft 的 Brian Harry 贴出了一个针对这个问题的很长的分析:http://discuss.develop.com/archives/wa.exe?A2=ind0010A&L=DOTNET&P=R28572
Chris Sells 对 Brian 贴子的答复在这里:http://discuss.develop.com/archives/wa.exe?A2=ind0010C&L=DOTNET&P=R983
5.3 .NET 为什么不提供确定化的析构?
因为垃圾收集算法。.NET 的垃圾收集器通过周期地扫描应用程序正在使用的所有对象的列表来工作。扫描过程中所有未被发现的对象就可以被销毁并释放内存。当对对象的最后一个引用撤销后,算法的这种实现使运行库不能立即得到通知?它只能在下一次清理堆时发现。
而且,这种算法尽可能少地进行垃圾收集,以便工作得最有效率。通常,堆容量的消耗会触发收集过程。
5.4 在 .NET 中缺少确定化的析构有问题吗?
这确实会影响组件的设计。如果你的对象需要昂贵或紧缺的资源 (例如对数据库的锁定),你需要提供某种方法让客户端在工作完成后能告诉对象以释放资源。Microsoft 建议,为此目的你应提供一个称为 Dispose () 的方法。然而,这样会在分布式对象中引起问题?在一个分布式系统中由谁来调用 Dispose () 方法?需要有某种形式的引用-计数机制或所有者管理机制来处理分布式对象?不幸的是运行库对此爱莫能助。
5.5 确定化的析构是否影响在被管理代码中使用 COM 对象?
是的。从被管理代码中使用 COM 对象时,你实际上是依赖垃圾收集器来最终释放你的对象。如果你的 COM 对象占有昂贵的资源且只能在最终释放对象后才能释放,你可能需要在你的对象上提供一个新接口以支持显式的 Dispose () 方法。
5.6 我听说应该避免使用 Finalize 方法,那么是否应该在我的类理实现 Finalize?
对垃圾收集器而言,拥有 Finalize 方法的对象比没有此方法的对象需要做更多的工作。同时也不保证对象 Finalized 的次序,所以对于从 Finalized 方法访问其它对象有不同的看法。最后,不能保证 Finalized 方法一定能被调用。所以,永远不应该依赖它来清理对象的资源。
Microsoft 建议使用以下方式:
public class CTest
{
public override void Dispose()
{
... // Cleanup activities
GC.SuppressFinalize(this);
}
protected override void Finalize()
{
Dispose();
}
}
一般情况下客户端调用 Dispose (),对象的资源被释放,并且通过调用 SuppressFinalize (),垃圾收集器被免除了对它进行 Finalize 的义务。在最不利的情况下,即客户端忘记了调用 Dispose (),有很大的机会通过垃圾收集器调用 Finalize () 来最终释放对象的资源。由于垃圾收集算法的缺陷,这看起来像是相当合理的处理办法了。
5.7 我有控制垃圾收集算法的手段吗?
有一点。System.GC 类提供了一对有趣的方法。第一个是 Collect 方法?它强制垃圾收集器立即收集所有未被引用的对象。另一个是 RequestFinalizeOnShutdown (),它告诉垃圾收集器在应用程序关闭时一定要对每个对象运行 Finalize () 方法。在应用程序关闭时,垃圾收集器一般优先选择快速的推出方式而不是调用 Finzlize (),所以这个方法能手工强制运行库多负一点责任。
如果你想验证这不仅仅是理论上的说法,是一十下面的测试程序:
using System;
class CTest
{
protected override void Finalize()
{
Console.WriteLine( "This is the Finalizer." );
}
}
class CApplication
{
public static void Main()
{
Console.WriteLine( "This is Main." );
CTest test = new CTest();
// GC.RequestFinalizeOnShutdown();
}
}
运行此程序,然后再去掉 GC.RequestFinalizeOnShutdown() 这一行前面的注释标记并重新运行,注意有什么不同……
5.8 我怎么知道垃圾收集器在做什么?
.NET 运行库中很多令人感兴趣的统计通过 'COM+ Memory' 性能对象输出。使用 Performance Monitor 查看它们。
6. 属性
6.1 什么是属性?
最少有两种类型的 .NET 属性。第一类我称其为 metadata 属性?它允许将某些数据附加到类或方法上。这些数据称为类的 metadata 的一部分,并且可以像类的其它 metadata 一样通过映射来访问。metadata 的另一种属性是 [serializable],将它附加到类上表示类的实例可以被串行化。
[serializable] public class CTest {}
另一种类型的属性是上下文属性。上下文类型的属性使用和 metadata 相似的语法,但实际上它们是不同的。上下文类型属性提供一种解释机制,通过这种机制,实例的活动和方法调用可以是预先处理和/或随后处理的。如果你了解 Keith Brown 的通用委托器你可能熟悉这种思想。
6.2 我能创建自己的 metadata 属性吗?
是的。简单地从 System.Attribute 导出一个类并将其标记为 AttributeUsage 属性。例如:
[AttributeUsage(AttributeTargets.Class)]
public class InspiredByAttribute : System.Attribute
{
public string InspiredBy;
public InspiredByAttribute( string inspiredBy )
{
InspiredBy = inspiredBy;
}
}
[InspiredBy("Andy Mc's brilliant .NET FAQ")]
class CTest
{
}
class CApp
{
public static void Main()
{
object[] atts = typeof(CTest).GetCustomAttributes();
foreach( object att in atts )
if( att is InspiredByAttribute )
Console.WriteLine( "Class CTest was inspired by {0}", _
((InspiredByAttribute)att).InspiredBy );
}
}
6.3 我能创建自己的 context 属性吗?
是的。看看 http://www.develop.com/dbox/dotnet/threshold/ 处的 Don Box 的例子 (叫作 CallThreshold) 和 http://www.razorsoft.net/ 处的 Perter Drayton 的 Tracehook.NET
7. 代码访问安全性
7.1 什么是代码访问安全性 (CAS)?
CAS 是 .NET 安全性模型的一部分,它确定一段代码是否允许被运行,以及当它运行是可以使用什么资源。例如,CAS 可以防止一个 .NET 的 Web applet 将你的硬盘格式化。
7.2 CAS 如何起作用?
CAS 安全策略设计两个关键概念?代码组和权限。每个 .NET 元件是特定 代码组的成员,并且每个代码组被授予由有名权限集所指定的权限。
例如,使用默认的安全策略时,一个从 Web 站点下载的控件属于“Zone - Internet”代码组,它保持由有名权限集“Internet”所定义的权限。(自然,有名权限集“Internet”表示一组受到严格限制的权限。)
7.3 谁定义 CAS 代码组?
Microsoft 定义了一些默认代码组,但你可以改变这些甚至创建你自己的代码组。要想看到你的系统中定义的代码组,可以从命令横行运行“caspol -lg”命令。再我的系统里它看起来像这些:
Level = Machine
Code Groups:
1. All code: Nothing
1.1. Zone - MyComputer: FullTrust
1.1.1. Honor SkipVerification requests: SkipVerification
1.2. Zone - Intranet: LocalIntranet
1.3. Zone - Internet: Internet
1.4. Zone - Untrusted: Nothing
1.5. Zone - Trusted: Internet
1.6. StrongName - 0024000004800000940000000602000000240000525341310004000003
000000CFCB3291AA715FE99D40D49040336F9056D7886FED46775BC7BB5430BA4444FEF8348EBD06
F962F39776AE4DC3B7B04A7FE6F49F25F740423EBF2C0B89698D8D08AC48D69CED0FC8F83B465E08
07AC11EC1DCC7D054E807A43336DDE408A5393A4855612