读书笔记:Effective C#
1. use property instead of public data members. Property is an another pattern of method per se. So in IL, property and data member have different implementation even if they share the same name, which means recompilation of codes is necessary if public data members are replacd with properties with the same name.
Interface can have properties defined in its body while data member not. i.e:public interface INameValue{object Name{get} object Value{get;set;}} Then, a class would implement the concrete calls for the property get and set methods.
2. Prefer readonly to const. Readonly defines runtime constants whereas const defines compile time constants. Const constants run faster since all const constants are replaced with the real value in compilation. And Readonly constants must be initialized in a constructor and can't be modified after the constrcution of a class. Another big difference is const varibles must be used for primitive type, int, string, enums, double (no new operator is allowed)...while runtime constant can be assgined with any type. Using compile time constants has somewhat compatiblity and flexibility problem, changing the value of a compile time constant might lead to a recompilation of all assemblies.
3. Use "as" rather than explicite converstion while casting object into types. Cast objects with as operator won't cause exception while explicite casting would. For the value of null, it could be casted into any type with explicit casting while it's not true with as operator.
4. Provide "toString" method, so that an object could be presented in a right way. Sometimes, a class could even implement IFormatProvider to present object information.
5. Distinguish Reference type from value type.
Value type is used for storing data, using Struct as declaror. It can't provide polymorphic mechanism but it works faster as it is stored in stack structure.
Reference type is used for providing responsibility to a project, defining behaivors of an object.
In c++, all types are value types and they're returned and passed by value. In Java, all types are reference type, they're returned and passed by reference. In c#, it's an interesting hybrid.
When to use value type: the principle use is for data storage; no subclass; no polymorphicism; all public member is impletemented through inferface;
6. Don't relize on finalizer to release unmanaged resourse. In .net environment, there's no deterministic finalization that could free unmanaged memmory instantaneously. When GC finds a garbage that requires finalization, it would put the garbage into a queue and spawns a new thread to execuate all the finalizers. On the next GC circle, those objects that have been finalized are eventually removed from memory, although it might cost a lot more time to be called.
7. Prefer variable initializers to assignment states in contructor. Here's the order of operations for constructing the first instance of a type:
static variable is set to 0 => execute static variable initializer => execute static constructors(from base to child) => instance variables are set to 0 => execute instance variable initializers => execute instance constructor(from base to child). Prefering variable initializers could save duplicate instructions while initializing a new instance.
8. Explicitely call Dispose method. Once unmanaged resource is used in a function, we should take the responsibility of realeasing them properly. The common idiom is to use "try and finally" or wrap the object referenced to unmanaged resoure in a "using" clause. If a type implements IDispose interface, then we are supposed to explicately execute it in the finally statement or get it run automatically by writing "using" clause.
9. Minimize garbage. There're three main ways to prevent unnecessary garbage objects. The first one is to promote local referenced objects that are heavily used within local methods to member variables. The second one is to create singleton objects that are reused over the application. The third is to set up a Builder(StringBuilder) class to facilitate the mutliphased construction of an immutable object.
10. Standard Dispose pattern. If an object contains unmanaged resources that are not possible to be released in local methods. Then we need to implement IDisposable interface so that the defensive code could be invoked if users forget to call the dispose method of the instance. Here's the difference between finalizer() and dispose(): dispose() method could be called by users explicitly in order to free those unmanaged resourse. finalizer() is used inplicitly to prevent memery leak. It provides a safe way to ensure unmanaged resource would eventually be released by GC even if the users forget to call dispose() method.
Here is a framework of this pattern:
public class MyResourceHog: IDisposable
{
private bool _alreadyDisposed = false; //flag
~MyResourceHog(){ Dispose(false); } // finalizer, it also calls a virtual dispose method, the parameter indicates that the call is from finalizer.
public void Dispose(){Dispose(true); GC.SuppressFinalize(this);} //implement IDisposable, SuppressFinalize means the GC would not execute finalizer of the object.
protected virtual void Dispose(bool isDisposing) // virtual method that could be overrided.
{
if (_alreadyDisposed) return; // do nothing if it is freed.
if (isDisposing){//TODO free managed resource here}
//TODO free unmanged resource here, for both finalizer and disposal.
_alreadyDisposed = true;
}
}
浙公网安备 33010602011771号