Top Ten Traps In C # for C Programmers Chinese Edition
Author: Jesse Liberty
Translator: glory
[Interportion: C # get started with the article. Please note: All program debugging environments are Microsoft Visual Studio.NET 7.0 Beta2 and Microsoft .NET Framework SDK Beta2. Limited to translator's time and ability, if there is a mistake in the text, when the original version is prevail]
In an article published on "MSDN Magazine" (July 2001), I told "transfer from C to C #, what should you understand?". [Translation: The Chinese version of this article can check "programmer" magazine .Net special issue]. In that article, I said that the grammar of C # and C is very like, the difficulties in the transfer process are not from the language itself, but the adaptation of the controlled .NET environment and understanding of the huge .NET framework.
I have edited a list of different points of C and C # syntax (I can find this list on my Web site. On the site, click Books to browse "Programming C #", or click FAQ to see). As you are expected, many grammar changes are small. Some changes are potential traps for careful C programmers. This article will focus on ten biggest hazards.
Trap 1. Non-determination end and C # destructors
For a reason, for most C programmers, the biggest difference in C # is garbage collection. This means you don't have to worry about memory leakage and to ensure that the pointer to the pointer. Of course, you will lose the precise control of when the object is destroyed. In fact, there is no explicit destructor in C #.
If you are dealing with an unregulated resource, you need to explicitly release those resources when you run out. The implicit control of resources can be called by the garbage collector when the object is destroyed by providing a Finalize method (called a terminator).
The terminal should only release the unregulated resource carried by the object, and other objects should not be referenced. Note: If you only have some controlled object references, you don't use the Finalize method - it is only used when you need to process uncontrolled resources. Because use the terminator to pay the price, you should only be implemented on the needs of the needs (that is, implementation of the cost of expensive, unregulated resources).
Never call the Finalize method directly (except for the Finalize method of the foundation class in your own class, the garbage collector will call it.
The destructor of the C # looks like a C destructor on the sentence, but they are different. The C # destructor is just a shortcut to declare the Finalize method and lock it to its base class. Will be called in turn, see the complete examples given later. Therefore, the following ways:
~ Myclass ()
{
// Do Work Here
}
And the following method has the same effect:
Myclass.finalize ()
{
// Do Work Here
Base.Finalize (); //
}
[Translation: The above code is obviously wrong, first should be written as:
Class myclass
{
void finalize ()
{
// Do Work Here
Base.Finalize (); // is not right! The compiler will tell you that the Finalize method of the base class can not be called directly, it will automatically call from the destructor. For reasons, see the examples behind this section and the relevant translation of the trap! }
}
Here is a complete example:
Using system;
Class RytestParCls
{
~ Rytestparcls ()
{
Console.WriteLine ("RytestParcls's Destructor);
}
}
Class RytestchldCls: rytestparcls
{
~ Rytestchldcls ()
{
Console.writeline ("RytestchldCls's Destructor);
}
}
Public Class RytestdstrcApp
{
Public static void main ()
{
RytestchldCls RTCC = new testchldcls ();
RTCC = NULL;
Gc.collect (); // Forced garbage collection
Gc.waitforpendingfinalizers (); // Hangs the current thread until the thread is cleared by the thread of the terminal.
Console.Writeline ("GC Completed!");
}
}
The above program output is:
RytestchldCls's Destructor
RytestParCls's Destructor
GC Completed!
Note: In the CLR, it is virtually used by overloading the virtual method of System.object, in C #, in C #, the method is not allowed to be overloaded or call it directly, and the following method is wrong:
Class RytestFinalClass
{
Override protected void finalize () {} // error! Do not overload the System.Object method.
}
Similarly, the following is also wrong:
Class RytestFinalClass
{
Public void selfinalize () // pay attention! This name is yourself, not Finalize
{
this.Finalize () // Error! Can't call Finalize directly ()
Base.Finalize () // Error! Can't call the base class finalize ()
}
}
Class RytestFinalClass
{
protected void finalize () // pay attention! This name is different from the above, at the same time, it is not Override, this is ok, so you hide the Finalize of the base class.
{
This.Finalize () // Toned himself, of course, but is this recursive call you want? J
Base.Finalize () // Error! Can't call the base class finalize ()
}
}
For a complete understanding of this topic, please refer to the trap. 】
Trap two. Finalize and Dispose
Explicit call terminators are illegal, and the Finalize method should be called by the garbage collector. If it is a limited, unregulated resource (such as a file handle), you may want to turn off and release it as quickly as possible, then you should implement the IDisposable interface. This interface has a Dispose method that performs a clear action. Class customers are responsible for explicitly calling this Dispose method. The Dispose method is equal to your customers say "Don't wait for Finalize, let's go now!" If you provide a Dispose method, you should disable the Finalize method of the garbage collector to call the object - since it is necessary to clear it. In order to do this, you should call the static method gc.suppressFinalize, and incompose the THIS pointer to the object, your Finalize method can call your Dispose method.
You may write this:
Public void dispose ()
{
// Perform a clear action
// Tell the garbage collector Do not call Finalize
Gc.suppressFinalize (this);
}
Public override void firmize ()
{
Dispose ();
Base.Finalize ();
}
[Translation: The above code is problematic, please refer to the example given in the trap. There is a very good article on the Microsoft Site, the statement and the basic consistent, but its code example is can't pass in Microsoft Visual Studio.net 7.0 Beta2 and Microsoft .NET Framework SDK Beta2, because there is no Beta1 comparison Therefore, it is still not possible to determine the pen error of the article, or because Beta1 and Beta2 are different. For example, this example (from Gozer The Destructor) cannot pass in the beta2 environment:
Class X
{
Public X (INT N)
{
THIS.N = N;
}
~ X ()
{
System.console.writeline ("~ x () {0}", n);
}
Public void dispose ()
{
Finalize (); // This line code is in the beta2 environment! Compiler prompt, can not call Finalize, consider calling idisposable.dispose (if available)
System.gc.suppressFinalize (this);
}
Private int N;
}
Class main
{
Static void f ()
{
X x1 = new x (1);
X x2 = new x (2);
x1.dispose ();
}
Static void main ()
{
f ();
System.gc.collect ();
System.gc.waitforpendingfinalizers ();
}
}
In this article, it claims that there will be the following output:
~ X () 1
~ X () 2
Why? 】
For some objects, you may prefer to let your customers call the Close method (for example, the file object is more meaningful than Dispose). Then you can create a private's Dispose method and a public CLOSE method and call Dispose in Close.
Because you can't affirm the customer will call Dispose, and the terminator is uncertain (you can't control when you run GC), C # provides the USING statement to ensure that Dispose is called as early as possible. This statement is used to declare what object you are using and create a scope for these objects with curly brackets. When arriving "}" J, the object's Dispose method will be automatically called: use system.drawing;
Class Tester
{
Public static void main ()
{
USING (Font thefont = New Font ("Arial, 10.0F))
{
// use thefont
} // Compiler to call Dispose for thefont
Font Anotherfont = New Font ("Courier", 12.0F);
Using (AnotherFont)
{
// Use Anotherfont
} // Compiler Call Dispose for anotherfont
}
}
In the first part of the above, theFont object is created in the USING statement. When the scope of the USING statement ends, the Dispose method of the Thefont object is called. The second part is created in the USING statement. When you decide to use the anotherfont object, you can put it in the USING statement, when reaching the role of the USING statement, the target's Dispose method is also called .
The USING statement also protects you unforeseen unusual exceptions, regardless of how to leave the USING statement, Dispose will be called, as if there is an implicit TRY-Catch-FinalLy block.
Trap three.c # distinguished value type and reference type
Like C , C # is a strong type of language. And like C , C # divides the type into two categories: the intrinsic (built-in) type and programmer-defined user-defined type of user-defined user-defined type [Translation:, called UDT].
In addition to distinguishing the inherent type and user-defined type, C # also district value types and reference types. Like the variables in C , the value type is saved on the stack, unless the value class is embedded in the object. The reference type variable itself is on the stack, but the objects they point to are on the heap, which is very like a pointer in C [translation: this is actually more like C reference J]. When transmitted to the method, the value type is a pass value (a copy) and the reference type is transmitted efficiently by reference.
Class and interface Create a reference type, but you must remember (see Trap 5): Like all inherent types, the structure is also a value type.
[Translation: You can see the trap five)
Trap four. Be wary of implicit boxes
Packing and unpacking is a process that enables value types (such as integers, etc.) that can be processed as reference types. The value is packaged into an object, and the subsequent unpacking is reduced to a value type. Each type in C # includes an inherent type that is derived from Object and can be implicitly converted to Object. Packing a value is equivalent to creating an object of an Object and copying the value into the object.
The boxes are implicit, so when you need a reference type and you provide a value type, this value will be implicit. Packing brought some implementation burden, so avoid packing as much as possible, especially in a big collection.
If you want to convert the name of the subject, you must explicitly remove it. The unpacking action is divided into two steps: first check the object instance to ensure that it is a package object of the value type that will be converted. If yes, the value is copied from the instance into the target value type variable. If you want to successfully remove the box, the unpackable object must be a reference to the box object of the target value type. Using system;
Public class unboxingtest
{
Public static void main ()
{
INT i = 123;
/ /
Object o = i;
/ / Unpack (must be explicit)
INT j = (int) O;
Console.WriteLine ("J: {0}", J);
}
}
If the unpackable object is NULL or a reference to the box object different from the target type, it will throw an InvalidCastException. [Translation: This is wrong here. If the object being removed is NULL, it will throw a system.nullreferenceException instead of system.invalidcastexcepiton.
【注: About this problem, I have more exciting description in another translation (a Comparrative Overview of C # Chinese version (superior)).
Trap five.C # structure is large
The structure in C is almost similar to the class. In C , the only difference is structure [translation: refer to member] By default, it is also possible for public access (rather than private) levels and inherits the default is also public (same, not private). Some C programmers regard the structure as only the object of only data, but this is not the agreement supported by the language itself, and this practice is also not encouraged by many OO designers.
In C #, the structure is a simple user-defined type, one very different from the lightweight optional option. Although structural support attributes, methods, fields, and operators, structures do not support inheritance or destructors.
More importantly, the class is a reference type, and the structure is a value type (see trap three). Therefore, the structure is very useful for objects that do not need to quote semantics. Using the structure in an array, it will be more efficient in memory, but if you are used in a collection, it is not that efficient. The collection requires a reference type, so if the structure is used in the collection, it must be packaged (see trap four), and the packing and unpacking requires additional burden, so in large collection, the class may be more effective .
[Translation: The following is a complete example, but also demonstrates implicit type conversion, please observe the program and its operation results J
Using system;
Class RytestCls
{
Public RytestCls (int aint)
{
THIS.INTFIELD = AINT;
}
Public Static Implicit Operator RytestCls (Ryteststt RTS)
{
Return New RytestCls (RTS.Intfield);
}
Private int intfield;
Public int INTPROPERTY
{
get
{
Return this.intfield;
}
set
{
THIS.INTFIELD = VALUE;
}
}
}
Struct Ryteststt
{
Public lyteststt (int aint)
{
THIS.INTFIELD = AINT;
}
Public int intfield;}
Class RyclsstTTestApp
{
Public Static Void ProcessCls (RytestCls RTC)
{
Rtc.intproperty = 100;
}
Public Static Void ProcessStt (Ryteststt RTS)
{
Rts.intfield = 100;
}
Public static void main ()
{
RytestCls RTC = New RyTestCls (0);
Rtc.intproperty = 200;
ProcessCls (RTC);
Console.writeline ("RTC.intProperty = {0}", RTC.intProperty;
Ryteststt RTS = new ity;
RTS.Intfield = 200;
ProcessSTT (RTS);
Console.writeline ("RTS.Intfield = {0}", RTS.Intfield;
Ryteststt RTS2 = New RytestStt (0);
RTS2.INTFIELD = 200;
ProcessCls (RTS2);
Console.writeline ("RTS2.INTFIELD = {0}", RTS2.INTFIELD);
}
}
The above program operation results are:
Rtc.intproperty = 100
Rtc.intfield = 200
RTS2.INTFIELD = 200
】