计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc

上传人:文库蛋蛋多 文档编号:2385512 上传时间:2023-02-17 格式:DOC 页数:19 大小:165.50KB
返回 下载 相关 举报
计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc_第1页
第1页 / 共19页
计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc_第2页
第2页 / 共19页
计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc_第3页
第3页 / 共19页
计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc_第4页
第4页 / 共19页
计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc_第5页
第5页 / 共19页
点击查看更多>>
资源描述

《计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc》由会员分享,可在线阅读,更多相关《计算机 资源管理 外文翻译 外文文献 英文文献 Effective C# 中文版改善C程序的50种方法第二章.NET.doc(19页珍藏版)》请在三一办公上搜索。

1、.NET Resource ManagementBillWagnerEffective C#: 50 Specific Ways to Improve Your C#,Chapter 2,.NET Resource Management,BillWagner,Addison Wesley Professional,2004,77115.NET 资源管理比尔瓦格拉Effective C# 中文版改善C#程序的50种方法,第二章,.NET 资源管理,比尔瓦格拉,2004,77115一个简单的事实:.Net应用程序是在一个托管的环境里运行的,这个环境和不同的设计器有很大的冲突,这就才有了Effect

2、ive C#。极大限度上的讨论这个环境的好处,须要把你对本地化环境的想法改变为.Net CLR。也就意味着要明白.Net的垃圾回收器。在你明白这一章里所推荐的内容时,有必要对.Net的内存管理环境有个大概的了解。那我们就开始大概的了解一下吧。垃圾回收器(GC)为你控制托管内存。不像本地运行环境,你不用负责对内存泄漏,不定指针,未初始化指针,或者一个其它内存管理的服务问题。但垃圾回收器前不是一个神话:你一样要自己清理。你要对非托管资源负责,例如文件句柄,数据链接,GDI+对象,COM对象,以及其它一些系统对象。这有一个好消息:因为GC管理内存,明确的设计风格可以更容易的实现。循环引用,不管是简单

3、关系还是复杂的网页对象,都非常容易。GC的标记以及严谨的高效算法可以检测到这些关系,并且完全的删除不可达的网页对象。GC是通过对从应用程序的根对象开始,通过树形结构的“漫游”来断定一个对象是否可达的,而不是强迫每个对象都保持一些引用跟踪,COM就是这样的。DataSet就是一个很好的例子,展示了这样的算法是如何简化并决定对象的所属关系的。DataSet是一个DataTable的集合,而每一个DataTable又是DataRow的集合,每一个DataRow又是DataItem的集合,DataColum定义了这些类型的关系。这里就有一些从DataItem到它的列的引用。而同时,DataTime也同

4、样有一个引用到它的容器上,也就是DataRow。DataRow包含引用到DataTable,最后每个对象都包含一个引用到DataSet。如果这还不够复杂,那可以创建一个DataView,它提供对经过过滤后的数据表的顺序访问。这些都是由DataViewManager管理的。所有这些贯穿网页的引用构成了DataSet。释放内存是GC的责任。因为.Net框架的设计者让你不必释放这些对象,这些复杂的网页对象引用不会造成问题。没有必须关心这些网页对象的合适的释放顺序,这是GC的工作。GC的设计结构可以简化这些问题,它可以识别这些网页对象就是垃圾。在应用程序结束了对DataSet的引用后,没有人可以引用到

5、它的子对象了(译注:就是DataSet里的对象再也引用不到了)。因此,网页里还有没有对象循环引用DataSet,DataTables已经一点也不重要了,因为这些对象在应用程序都已经不能被访问到了,它们是垃圾了。垃圾回收器在它独立的线程上运行,用来从你的程序里移除不使用的内存。而且在每次运行时,它还会压缩托管堆。压缩堆就是把托管堆中活动的对象移到一起,这样就可以空出连续的内存。图2.1展示了两个没有进行垃圾回收时的内存快照。所有的空闲内存会在垃圾回收进行后连续起来。图2.1 垃圾回收器不仅仅是移动不使用的内存,还移除动其它的对象,从而压缩使用的内存,让出最多的空闲内存。 正如你刚开始了解的,垃圾

6、回收器的全部责任就是内存管理。但,所有的系统资源都是你自己负责的。你可以通过给自己的类型定义一个析构函数,来保证释放一些系统资源。析构函数是在垃圾回收器把对象从内存移除前,由系统调用的。你可以,也必须这样来释放任何你所占用的非托管资源。对象的析构函数有时是在对象成为垃圾之后调用的,但是在内存归还之前。这个非确定的析构函数意味着在你无法控制对象析构与停止使用之间的关系(译注:对象的析构与对象的无法引用是两个完全不同的概念。关于GC,本人推荐读者参考一下Jeffrey的.Net框架程序设计(修订版)中讨论的垃圾回收器)。对C+来说这是个重大的改变,并且这在设计上有一个重大的分歧。有经验的C+程序员

7、写的类总在构造函数内申请内存并且在析构函数中释放它们:/ 好的 C+, 坏的C#:class CriticalSectionpublic: / 构造系统需要的资源 CriticalSection( ) EnterCriticalSection( ); / 销毁资源 CriticalSection( ) ExitCriticalSection( ); ;/ 使用:void Func( ) / 系统资源的生存周期 CriticalSection s; / Do work. /. / compiler generates call to destructor. / code exits critic

8、al section.这是一种很常见的C+风格,它保证资源无异常的释放。但这在C#里不工作,至少,与这不同。明确的析构函数不是.Net环境或者C#的一部份。强行用C+的风格在C#里使用析构函数不会让它正常的工作。在C#里,析构函数确实是正确的运行了,但它不是即时运行的。在前面那个例子里,代码最终在critical section上,但在C#里,当析构函数存在时,它并不是在critical section上。它会在后面的某个未知时间上运行。你不知道是什么时候,你也无法知道是什么时候。依懒于析构函数同样会导致性能上的损失。须要析构的对象在垃圾回收器上放置了一剂性能毒药。当GC发现某个对象是垃圾但是

9、须要析构时,它还不能直接从内存上删除这个对象。首先,它要调用析构函数,但析构函数的调用不是在垃圾回收器的同一个线程上运行的。取而代之的是,GC不得不把对象放置到析构队列中,让另一个线程让执行所有的析构函数。GC继续它自己的工作,从内存上移除其它的垃圾。在下一个GC回收时,那些被析构了的对象才会再从内存上移除。图2.2展示了三个内存使用不同的GC情况。注意,那些须要析构的对象会待在内存里,直到下一次GC回收。图2.2 这个顺序展示了析构函数在垃圾回收器上起的作用。对象会在内存里存在的时间更长,须要启动另一个线程来运行垃圾回收器。这用使你相信:那些须要析构的对象在内存至少多生存一个GC回收循环。但

10、,我是简化了这些事。实际上,因为另一个GC的介入(译注:其实只有一个GC,作者是想引用回收代的问题。),使得情况比这复杂得多。.Net回收器采用”代“来优化这个问题。代可以帮助GC来很快的标识那些看上去看是垃圾的对象。所以从上一次回后开始创建的对象称为第0代对象,所有那些经过一次GC回收后还存在的对象称为第1代对象。所有那些经过2次或者2次以上GC回收后还存在的对象称为第2代对象。分代的目的就是用来区分临时变量以及一些应用程序的全局变量。第0代对象很可能是临时的变量。成员变量,以及一些全局变量很快会成为第1代对象,最终成为第2代对象。GC通过限制检测第1以及第2代对象来优化它的工作。每个GC循

11、环都检测第0代对象。粗略假设个GC会超过10次检测来检测第0代对象,而要超过100次来检测所有对象。再次考虑析构函数的开销:一个须要析构函数的对象可能要比一个不用析构函数的对象在内存里多待上9个GC回收循环。如果它还没有被析构,它将会移到第2代对象。在第2代对象中,一个可以生存上100个GC循环直到下一个第2代集合。结束时,记得一个垃圾回收器负责内存管理的托管环境的最大好处:内存泄漏,其它指针的服务问题不在是你的问题。非内存资源迫使你要使用析构函数来确保清理非内存资源。析构函数会对你的应用程序性能产生一些影响,但你必须使用它们来防止资源泄漏(译注:请注意理解非内存资源是什么,一般是指文件句柄,

12、网络资源,或者其它不能在内存中存放的资源)。通过实现IDisposable接口来避免析构函数在垃圾回收器上造成的性能损失。接下来的具体的原则将会帮助你更有效的使用环境来开发程序。Effective C# 原则12:选择变量初始化而不是赋值语句一些类经常不只一个构造函数。时间一长,就难得让它的成员变量以及构造函数进行同步了。最好的确保这样的事不会发生的方法就是:在声明就是的时间就直接初始化,而不是在每个构造函数内进行赋值。而且你应该使用初始化器语法同时为静态的和实例的变量进行初始化。在C#里,当你声明一个变量时就自然的构造了这个成员变量。直接赋值:public class MyClass / d

13、eclare the collection, and initialize it. private ArrayList _coll = new ArrayList( );忽略你最终会给MyClass添加多少个构造函数,_coll会正确的初始化。编译器会产生一些代码,使得在你的任何一个构造函数调用前,都会初始化你声明的实例变量。当你添加一个新的构造函数时,_coll就给你初始化了。当你添加了一个新的变量,你不用在所有的构造函数里添加初始化代码;直接在声明的地方对它进行初始化就行了。同样重要的是:如果你没有明确的声明任何一个构造函数,编译会默认的给你添加一个,并且把所有的变量初始化过程都添加到这个

14、构造函数里。初始化器更像是一个到构造函数的方便的快捷方法。初始化生成的代码会放置在类型的构造函数之前。初始化会在执行类型的基类的构造函数之前被执行,并且它们是按你声明的先后关系顺序执行的。使用初始化器是一个最简单的方法,在你的类型里来避免使用一些没有赋值的变量,但这并不是很好。下面三种情况下,你不应该使用初始化器语法。首先就是,如果你是初始化一个对象为0,或者为null。系统默认会在你任何代码执行前,为所有的内容都初始化为0。系统置0的初始化是基于底层的CPU指令,对整个内存块设置。你的任何其它置0的初始化语句是多余的。C#编译器忠实的添加额外的指令把内存设置为0。这并没有错,只是效率不高。事

15、实上,如果是处理值类型数据,这是很不值的:MyValType _MyVal1; / initialized to 0MyValType _MyVal2 = new MyValType(); / also 0两条语句都是把变量置为0。第一个是通过设置包含_MyVal1的内存来置0;而第二个是通过IL指令initobj,这对变量_MyVal2会产生装箱与拆箱操作。这很要花一点额外的时间(参见原则17)。第二个低效率的是在你为一个对象添加两个构造函数时会产生。你使用初始化器初始化变量,而所有的构造函数也对这些变量进行了初始化。这个版本的MyClass两个不同的ArrayList对象在它的构造函数内:

16、public class MyClass / declare the collection, and initialize it. private ArrayList _coll = new ArrayList( ); MyClass( ) MyClass( int size ) _coll = new ArrayList( size ); 当你创建一个新的MyClass对象时,特别指定集合的大小,你创建了两个数组列表。其中一个很快成为垃圾对象。初始化器在所有的构造函数之前会执行,构造函数会创建第2个数组列表。编译器产生了这个的一个版本,当然这是你决不会手动写出来的。public class

17、MyClass / declare the collection, and initialize it. private ArrayList _coll; MyClass( ) _coll = new ArrayList( ); MyClass( int size ) _coll = new ArrayList( ); _coll = new ArrayList( size ); 最后一个原因要把初始化放到构造函数里就是促使异常的捕获。你不能在初始化器中使用try块,任何在构造时因成员变量产生的异常可能衍生到对象的外面。你无法试图在你的类里来捕获它。你应该把那些初始化代码移到构造函数里,这样你

18、就可以捕获异常从而保证你的代码很友好(参见原则45)。变量初始化器是一个最简单的方法,在忽略构造函数时来保证成员变量被正确的初始化。初始化器在所有的构造函数之前被执行。使用这样的语法意味着当你在为后来发布的版本中添加了构造函数时,不会忘记添加恰当的初始化到构造函数里。当构造函数与初始化生成同样的成员对象时,就使用初始化器。阅读简单而且易于维护。Effective C# 原则13:用静态构造函数初始化类的静态成员你应该知道,在一个类型的任何实例初始化以前,你应该初始化它的静态成员变量。在里C#你可以使用静态的预置方法和静态构造函数来实现这个目的。一个类的静态构造函数是一个与众不同的,它在所有的方

19、法,变量或者属性访问前被执行。你可以用这个函数来初始化静态成员变量,强制使用单件模式,或者实现其它任何在类型的实例可用前应该完成的工作。你不能用任何的实例构造函数,其它特殊的私有函数, 或者任何其它习惯方法来初始化一个变量。和实例的预置方法一样,你可以把静态的预置方法做为静态构造函数可替代的选择。如果须要简单的分配一个静态成员,就直接使用初始化语法。当你有更复杂的逻辑来初始化静态成员变量时,就创建一个静态构造函数:public class MySingleton private static readonly MySingleton _theOneAndOnly = new MySinglet

20、on( ); public static MySingleton TheOnly get return _theOneAndOnly; private MySingleton( ) / remainder elided可以用下面的方法简单的实现单件模式,实际上你在初始化一个单件模式时可能有更复杂的逻辑:public class MySingleton private static readonly MySingleton _theOneAndOnly; static MySingleton( ) _theOneAndOnly = new MySingleton( ); public stati

21、c MySingleton TheOnly get return _theOneAndOnly; private MySingleton( ) / remainder elided同样,和实例的预置方法一样,静态的预置方法在静态的构造函数调用前执行。并且,你的静态预置方法在基类的静态构造函数执行前被执行。当应用程序第一次装载你的数据类型时,CLR自动调用静态构造函数。你只能定义一个静态构造函数,并且不能有参数。因为静态构造函数是CLR调用的,你必须十分注意异常的产生。如果在静态构造函数里产生了异常,CLR将会直接终止你的应用程序。正因为异常,静态构造函数常常代替静态预置方法。如果你使用静态预置

22、方法,你自己不能捕获异常。做为一个静态的构造,你可以这样(参见原则45):static MySingleton( ) try _theOneAndOnly = new MySingleton( ); catch / Attempt recovery here. 静态预置方法和静态构造函数为你的类提供了最清爽的方法来初始化静态成员。与其它语言不同,它们被添加到C#语言中,是初始化静态成员的两个不同的特殊位置。The simple fact that .NET programs run in a managed environment has a big impact on the kinds o

23、f designs that create effective C#. Taking utmost advantage of that environment requires changing your thinking from native environments to the .NET CLR. It means understanding the .NET Garbage Collector. An overview of the .NET memory management environment is necessary to understand the specific r

24、ecommendations in this chapter, so lets get on with the overview.The Garbage Collector (GC) controls managed memory for you. Unlike native environments, you are not responsible for memory leaks, dangling pointers, uninitialized pointers, or a host of other memory-management issues. But the Garbage C

25、ollector is not magic: You need to clean up after yourself, too. You are responsible for unmanaged resources such as file handles, database connections, GDI+ objects, COM objects, and other system objects.Heres the good news: Because the GC controls memory, certain design idioms are much easier to i

26、mplement. Circular references, both simple relationships and complex webs of objects, are much easier. The GCs Mark and Compact algorithm efficiently detects these relationships and removes unreachable webs of objects in their entirety. The GC determines whether an object is reachable by walking the

27、 object tree from the applications root object instead of forcing each object to keep track of references to it, as in COM. The DataSet class provides an example of how this algorithm simplifies object ownership decisions. A DataSet is a collection of DataTables. Each DataTable is a collection of Da

28、taRows. Each DataRow is a collection of DataItems. Each DataTable also contains a collection of DataColumns. DataColumns define the types associated with each column of data. There are other references from the DataItems to its appropriate column. Every DataItem also contains a reference to its cont

29、ainer, the DataRow. DataRows contain references back to the DataTable, and everything contains a reference back to the containing DataSet.If thats not complicated enough, you can create DataViews that provide access to filtered sequences of a data table. Those are all managed by a DataViewManager. T

30、here are references all through the web of objects that make up a DataSet. Releasing memory is the GCs responsibility. Because the .NET Framework designers did not need to free these objects, the complicated web of object references did not pose a problem. No decision needed to be made regarding the

31、 proper sequence of freeing this web of objects; its the GCs job. The GCs design simplifies the problem of identifying this kind of web of objects as garbage. After the application releases its reference to the dataset, none of the subordinate objects can be reached. It does not matter that there ar

32、e still circular references to the DataSet, DataTables, and other objects in the web. Because these objects cannot be reached from the application, they are all garbage.The Garbage Collector runs in its own thread to remove unused memory from your program. It also compacts the managed heap each time

33、 it runs. Compacting the heap moves each live object in the managed heap so that the free space is located in one contiguous block of memory. Figure 2.1 shows two snapshots of the heap before and after a garbage collection. All free memory is placed in one contiguous block after each GC operation.Fi

34、gure 2.1. The Garbage Collector not only removes unused memory, but it moves other objects in memory to compact used memory and maximize free space.As youve just learned, memory management is completely the responsibility of the Garbage Collector. All other system resources are your responsibility.

35、You can guarantee that you free other system resources by defining a finalizer in your type. Finalizers are called by the system before an object that is garbage is removed from memory. You canand mustuse these methods to release any unmanaged resources that an object owns. The finalizer for an obje

36、ct is called at some time after it becomes garbage and before the system reclaims its memory. This nondeterministic finalization means that you cannot control the relationship between when you stop using an object and when its finalizer executes. That is a big change from C+, and it has important ra

37、mifications for your designs. Experienced C+ programmers wrote classes that allocated a critical resource in its constructor and released it in its destructor:/ Good C+, bad C#:class CriticalSectionpublic: / Constructor acquires the system resource. CriticalSection( ) EnterCriticalSection( ); / Dest

38、ructor releases system resource. CriticalSection( ) ExitCriticalSection( ); ;/ usage:void Func( ) / The lifetime of s controls access to / the system resource. CriticalSection s; / Do work. /. / compiler generates call to destructor. / code exits critical section.This common C+ idiom ensures that re

39、source deallocation is exception-proof. This doesnt work in C#, howeverat least, not in the same way. Deterministic finalization is not part of the .NET environment or the C# language. Trying to force the C+ idiom of deterministic finalization into the C# language wont work well. In C#, the finalize

40、r eventually executes, but it doesnt execute in a timely fashion. In the previous example, the code eventually exits the critical section, but, in C#, it doesnt exit the critical section when the function exits. That happens at some unknown time later. You dont know when. You cant know when.Relying

41、on finalizers also introducesperformance penalties. Objects that require finalization put a performance drag on the Garbage Collector. When the GC finds that an object is garbage but also requires finalization, it cannot remove that item from memory just yet. First, it calls the finalizer. Finalizer

42、s are not executed by the same thread that collects garbage. Instead, the GC places each object that is ready for finalization in a queue and spawns yet another thread to execute all the finalizers. It continues with its business, removing other garbage from memory. On the next GC cycle, those objec

43、ts that have been finalized are removed from memory. Figure 2.2 shows three different GC operations and the difference in memory usage. Notice that the objects that require finalizers stay in memory for extra cycles.Figure 2.2. This sequence shows the effect of finalizers on the Garbage Collector. O

44、bjects stay in memory longer, and an extra thread needs to be spawned to run the Garbage Collector. This might lead you to believe that an object that requires finalization lives in memory for one GC cycle more than necessary. But I simplified things. Its more complicated than that because of anothe

45、r GC design decision. The .NET Garbage Collector defines generations to optimize its work. Generations help the GC identify the likeliest garbage candidates more quickly. Any object created since the last garbage collection operation is a generation 0 object. Any object that has survived one GC oper

46、ation is a generation 1 object. Any object that has survived two or more GC operations is a generation 2 object. The purpose of generations is to separate local variables and objects that stay around for the life of the application. Generation 0 objects are mostly local variables. Member variables a

47、nd global variables quickly enter generation 1 and eventually enter generation 2.The GC optimizes its work by limiting how often it examines first- and second-generation objects. Every GC cycle examines generation 0 objects. Roughly 1 GC out of 10 examines the generation 0 and 1 objects. Roughly 1 GC cycle out of 100 examines all objects. Think about finalization and its cost again: An object that requires finalization might stay in memory for nine GC cycles more than it would if it did not require finalization. If it still has not been finalized, i

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 建筑/施工/环境 > 项目建议


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号