The 10 most common mistakes a C # programmer makes

Source: Internet
Author: User

About C #

C # is one of the few languages that reach the Microsoft Common Language Runtime (CLR). Achieving the CLR's language can benefit from the features it brings, such as cross-language integration, exception handling, security enhancements, a simple model of component composition, and debugging and Analysis Services. As a modern CLR language, C # is the most widely used application scenario for complex, professional development projects such as Windows desktops, mobile phones, and server environments.

C # is an object-oriented, strongly typed language. C # has strong type checking at compile and run time, so that most typical programming errors can be discovered early, and location positioning is quite accurate. It can save programmers a lot of time, compared to those who don't stick to the type, and then report a language that can be traced to inexplicable errors long after a violation has been done. However, many programmers intentionally or unintentionally abandon this detection somewhat, which leads to some of the issues discussed in this article.

About this article

This article describes the errors that 10 C # programmers often make, or traps that should be avoided.

Although most of the errors discussed in this article are for C #, some errors are related to other CLR-targeted languages, or to the language of the Framework Class Library (FCL).

Common error #1: Use references as values, or vice versa

Programmers in C + + and many other languages are accustomed to assigning values to variables, either by assigning a simple value or by referring to an existing object. However, in C #, a value or a reference is determined by the programmer who writes the object, rather than the programmer who instantiates the object and assigns the value. This tends to pit the novice programmer into C #.

If you do not know whether the object you are using is a value type or a reference type, you may experience some surprises. For example:

   point point1 = new point (20, 30);  point point2  = point1;  point2. X = 50;  console.writeline (point1. X);       // 20  (does this surprise you?)   console.writeline (Point2. X);        // 50    pen pen1 = new  pen (Color.Black);   pen pen2 = pen1;  pen2. Color = color.blue;  console.writeline (pen1. Color);     // blue  (or does this surprise you?)   console.writeline (Pen2. Color);      // blue 

As you can see, although the point and Pen objects are created the same way, the value of point1 remains the same when a new x's coordinate value is assigned to Point2. And when a new color value is assigned to the PEN2,PEN1, the change is also followed. Therefore, we can infer that each of Point1 and Point2 contains a copy of its own point object, while Pen1 and Pen2 refer to the same Pen object. Without this test, how can we know this principle?

One way is to look at how the object is defined (in Visual Studio, you can put the cursor on the name of the object and press the F12 key)

Public struct-point {...}        Defines a "value" type public class Pen {...} Defines a "reference" type

As shown above, in C #, the struct keyword is used to define a value type, and the class keyword is used to define the reference type. for those of you who have C + + programming backgrounds, it might be a surprise to get confused by some similar keywords between C + + and C #.

If the behavior you want to rely on depends on the value type and the reference type, for example, if you want to pass an object as an argument to a method and modify the state of the object in this method. You must make sure that you are dealing with the correct type object.

Common error # #: Misunderstanding default value of uninitialized variable

In C #, a worthy type cannot be empty. By definition, a value of type value, or even a value type that initializes a variable, must have a value. This is known as the default value for this type. This usually leads to the following, unexpected results when checking whether a variable is uninitialized:

Class Program {static point point1;      Static Pen Pen1;      static void Main (string[] args) {Console.WriteLine (pen1 = = null);    True Console.WriteLine (point1 = = null);      False (huh?) }  }

Why isn't "point 1" empty? The answer is that the point is a value type, and the default value point (0,0) has no null value. Fail to realize that this is a very simple and common mistake in C #

Many (but not all) value types have a "IsEmpty" attribute, and you can see that it equals the default value:

Console.WriteLine (Point1.        IsEmpty); True

When you check if a variable has been initialized, make sure that you know that the value is uninitialized is the type of the variable, and will not be null by default.

Common error #3: comparing strings with inappropriate or unspecified methods

There are many ways to compare strings in C #.

Although many programmers use the = = operator to compare strings, this approach is actually the least recommended. The main reason is that this method does not appear in the code to specify which type to use to compare strings.

Conversely, it is best to use the Equals method in C # to determine whether a string is equal:

public bool Equals (string value); public bool Equals (string value, StringComparison comparisonType);

The first Equals method (which does not comparisontype this parameter) is the same as the result of using the = = operator, but the advantage is that it explicitly indicates the type of comparison. It compares the strings sequentially by byte. In many cases, this is exactly the type of comparison you expect, especially when comparing some of the strings that are programmed, like filenames, environment variables, attributes, and so on. In these cases, it is only possible to compare the bytes by byte in order. The only downside to using the Equals method without the Comparisontype parameter is that the person who reads your program code may not know what your comparison type is.

Using the Equals method with Comparisontype to compare strings will not only make your code clearer, but will also allow you to consider what type of string to compare with. This method is well worth your use, because although there is not much difference between the sequential comparison and the locale-by-region comparisons in English, there may be a big difference in some other languages. If you neglect this possibility, you are undoubtedly digging a lot of "pits" for yourself on the road ahead. For example:

   string s =  "Strasse";    // outputs false:   console.writeline (s ==  "straße");   console.writeline (S.Equals ("stra& Szlig;e "));   console.writeline (S.equals (" Straße ",  stringcomparison.ordinal));   console.writeline (S.equals ("Straße",  stringcomparison.currentculture));           console.writeline (S.equals ("Straße",  StringComparison.OrdinalIgnoreCase));    // outputs true:   Console.WriteLine (S.equals ("Straße",  stringcomparison.currentculture));   Console.WriteLine (S.equals ("Straße",  stringcomparison.currentcultureignorecase)); 

The safest practice is to always provide a comparisontype parameter for the Equals method.

Here are some basic guidelines:

Use localized comparisons when comparing strings entered by a user or displaying string comparisons to users (currentculture  or Currentcultureignorecase )。

When comparing strings for programming, use the original comparison (ordinal  or  ordinalignorecase)

InvariantCulture and invariantcultureignorecase are generally not used unless in a restricted context, because the original comparison is usually more efficient. If the comparison with the local culture is essential, it should be implemented as a comparison based on the current culture or another particular culture.

In addition, for the equals  method, strings often provide a compare method that provides relative order information for a string and not just tests for equality. This method works well for <, <=, > and >=  operators and is equally applicable to the above discussion.

Common misconceptions #4: use iterative (rather than declarative) statements to manipulate collections

In C # 3.0, the introduction of LINQ changed our previous query and modification of collection objects. For From there, you should use LINQ to manipulate the collection, rather than iterating through the way.

Some C # programmers don't even know the existence of LINQ, but people who don't know it are gradually decreasing. But some people mistakenly think that LINQ is used only in database queries, because LINQ's keywords and SQL statements are so much alike.

Although the query operation of a database is a very typical application of LINQ, it can also be applied to a variety of enumerable collection objects. (for example, any object that implements the IEnumerable interface). For example, if you have an array of account types, do not write as follows:

   decimal total = 0;  foreach   (account account in myaccounts)  {    if  (account. status ==  "active")  {      total += account. balance;    }  } 

You just write this:

   decimal total =  ( from account in myaccounts                where account. status ==  "Active"                  Select account. Balance). Sum (); 

While this is a very simple example, in some cases a single LINQ statement can easily replace dozens of statements in one iteration loop (or nested loop) in your code. Less code often means that the opportunity to generate bugs is less likely to be introduced. However, keep in mind that there may be tradeoffs in terms of performance. In scenarios where performance is critical, especially if your iteration code can make assumptions about your collection, LINQ does not, so be sure to compare performance between the two methods.

#5常见错误: The underlying object is not considered in LINQ statements

For handling abstract manipulation Collection tasks, LINQ is undoubtedly huge. Whether they are objects in memory, database tables, or XML documents. In such a perfect world, you don't need to know the underlying object. The mistake here, however, is to assume that we live in a perfect world. In fact, the same LINQ statement can return different results when executed on the exact same data if the data happens to be in a different format.

For example, consider the following statement:

?
123

Imagine what happens to an account in one of the objects. The status equals "valid" (note uppercase a)?

Well, if Myaccout is the object of Dbset. (A different case-sensitive configuration is set by default), and the where expression still matches that element. However, if the myaccout is in a memory array, it will not match and therefore will produce different total results.

Wait a minute, in the string comparison we discussed earlier, we see that the = = Operator plays a simple comparison. So why, in this condition, is another form?

The answer to the

is that when the underlying object in the LINQ statement references the data in the SQL table (as in this example, in the case of an object with an Entity framework of Dbset), the statement is converted to a T-SQL statement. Then follow the rules of T-SQL, not C # rules, so the comparison in the above case is not case-sensitive.

Generally, even though LINQ is a useful and consistent way to query a collection of objects, in reality you need to know whether your statements will be translated into anything other than the C # engine or other expressions to ensure that your code behaves as expected at run time.

Common error #6: Confused with extension methods or deceived by its form

As mentioned earlier, the LINQ state relies on the implementation object of the IEnumerable interface, for example, the following simple function aggregates the account balances in the Account collection:

Public decimal sumaccounts (ienumerable<account> myaccounts) {return myaccounts.sum (a = a.balance); }

In the preceding code, the type of the myaccounts parameter is declared as ienumerable<account>,myaccounts refers to a sum method (C # uses a similar "dot notation" reference method or a class in an interface). We expect to define a sum () method in the Ienumerable<t> interface. However,,ienumerable<t> does not provide any references to the sum method and has only a concise definition as follows:

Public interface Ienumerable<out t>: IEnumerable {ienumerator<t> GetEnumerator (); }

But where should the sum method be defined? C # is a strongly typed language, so if the reference to the sum method is not valid, the C # compiler will error it. We know it must exist, but where should it be? Also, where are all the methods provided by LINQ for querying and aggregating results defined?

The answer is that sum is not defined within the IEnumerable interface, but rather a

Static methods defined in the System.Linq.Enumerable class (called "extension method")

  namespace System.Linq {    public static class  enumerable {      ...      // the  reference here to  "This ienumerable<tsource> source"  is       // the magic sauce that provides access to the  extension method sum      public static decimal sum< Tsource> (this ienumerable<tsource> source,                                           func<tsource,  decimal> selector);       ...    }  } 

But what is the difference between an extension method and other static methods, and what makes sure that we can access it in other classes?

The salient feature of the extension method is the this modifier before the first formal parameter. This is the "secret" of the compiler knowing that it is an extension method. The type of the parameter it modifies (ienumerable<tsource> in this example) indicates that the class or interface will appear to implement this method.

(It should also be noted that there is nothing strange about the similarity between the IEnumerable interface that defines the extension method and the name of the enumerable class.) This similarity is just a casual style choice. )

Having understood this, we can see that the Sumaccounts method described above can be implemented in the following way:

Public decimal sumaccounts (ienumerable<account> myaccounts) {return enumerable.sum (myaccounts, a = A.balan  CE); }

As a matter of fact, we may have implemented this approach, rather than asking what an extension method would be. The extension method itself is just a convenience for C # You do not have to inherit, recompile, or modify the original code to give the existing way to "add" the type.

The extension method is introduced into the scope by adding a using [namespace] at the beginning of the file. You need to know the namespace of the extension method you are looking for. It's easy if you know what you're looking for.

When the C # compiler encounters an instance of an object calling a method, and it does not find the method in the object's class, it tries to find a match for the required class and method signature in all extension methods in the scope. If found, it passes the instance reference as the first argument to the extension method, and then, if there are any other arguments, it is passed to the extension method in turn. (If the C # compiler does not find the appropriate extension method in the scope, it will be thrown.) )

For the C # compiler, the extension method is a "syntactic sugar" that allows us to write the code clearer and easier to maintain (in most cases). Obviously, the premise is that you know how to use it, otherwise it will be more confusing, especially in the beginning.

The application extension approach does have an advantage, but it can also cause headaches for developers who don't understand it or know it correctly, wasting time. Especially when looking at online sample code, or other code that has already been written. When the code generates a compilation error (because it invokes methods that are clearly not defined in the called type), the general tendency is to consider whether the code should be applied to other versions of the referenced class library, or even to a different class library. A lot of time is spent looking for a new version, or a class library that is considered "lost".

The name of the extension method is the same as the name of the method defined in the class, but only when there is a slight difference in the method signature, even the developers who are familiar with the extension method occasionally make the error. A lot of time will be spent looking for "nonexistent" spelling mistakes.

In C #, extension methods are becoming more and more popular. In addition to LINQ, the extension method is also applied in the two other class library unity Application Block and Web API frameworks that are now widely used by Microsoft, and there are many others. The more new the framework, the more likely it is to use the extension method.

Of course, you can also write your own extension method. However, it is important to realize that although the extension method appears to be called just like any other instance method, it is actually illusory. In fact, extension methods cannot access the private and protected members of the extended class, so it cannot be used as a substitute for traditional inheritance.

Common error #7: Using the wrong collection type for the task at hand

C # provides a large collection of object types, with only a subset of them listed below:
Array,arraylist,bitarray,bitvector32,dictionary<k,v>,hashtable,hybriddictionary,list<t>, Namevaluecollection,ordereddictionary,queue, Queue<t>,sortedlist,stack, Stack<t>,stringcollection, StringDictionary.

But in some cases, too many choices are as bad as not enough choices, and so are the collection types. A large number of options will certainly ensure that your work is working properly. But you'd better spend some time searching and understanding the collection type in advance to choose a collection type that best suits your needs. This will eventually make your program perform better and reduce the likelihood of errors.

If you have a collection that specifies an element type (such as a string or bit) and you are working on it, you should prefer to use it first. This collection is more efficient when the corresponding element type is specified.

In order to take advantage of type safety in C #, you'd better choose to use a generic interface instead of using non-generic excuses. The element type in a generic interface is the type you specify when declaring an object, not the element in a generic type. When you use a non-generic interface, the C # compiler cannot type-check your code. Similarly, when you manipulate collections of native types, using a non-generic interface causes C # to take frequent boxing (boxing) and unboxing (unboxing) operations on those types. This can have a noticeable performance impact when compared to a generic collection that specifies the appropriate type.

Another common pitfall is to implement a collection type on its own. This is not to say never do it, you can use or extend it. NET provides some of the widely used collection types to save a lot of time, rather than to reinvent the wheel.   in particular, C # 's C5 Generic Collection library  and CLI provide a number of additional collection types, such as persistent tree data structures, heap-based priority queues, array lists of hash indexes, linked lists, and more.

Common error #: Missing resource release

The CLR hosting environment plays the role of the garbage collector, so you do not need to explicitly release the memory used by the objects you have created. In fact, you cannot explicitly release it. There is no operator in C # that corresponds to the C + + DELETE or a method that corresponds to the free () function in the C language. But that doesn't mean you can ignore all the objects you've used. Many object types encapsulate many other types of system resources (for example, disk files, data connections, network ports, and so on). Maintaining these resource usage states can drastically deplete the system's resources, weaken performance, and eventually cause program errors.

Although destructors are defined in all C # classes, the problem with destroying objects (also called finalizers in C #) is that you are not sure that they will be called. They are called by the garbage collector at an uncertain time in the future (an asynchronous thread that may cause additional concurrency). Attempting to avoid this enforced restriction imposed by the Gc.collect () method in the garbage collector is not a good programming practice because it is possible for a garbage collection thread to block a thread in an unpredictable time when it attempts to reclaim an object that is appropriate for recycling.

This also means that it is best not to use finalizers and explicitly release resources without causing any of these consequences. When you open a file, network port, or data connection, when you no longer use these resources, you should explicitly release these resources as soon as possible.

Resource leaks can cause concern in almost all environments. However, C # provides a robust mechanism for making resources easier to use. If used rationally, it can greatly increase the probability of leakage. The NET framework defines a IDisposable interface that consists of only one Dispose (). Any object that implements the IDisposable interface calls the Dispose () method at the end of the object life cycle. The invocation results are clear and decisive for freeing up the resource used.

If you create and release an object in a code snippet and forget to call the Dispose () method, it is inexcusable because C # A using statement is provided to ensure that the Dispose () method is invoked regardless of the way the code exits (either an exception, a return statement, or a simple code snippet end). This using and the one mentioned earlier are used to introduce namespaces at the beginning of the file. It has another, completely unrelated purpose that many C # developers are unaware of, which is to ensure that the object's Dispose () method is called when the code exits:

   using  (filestream  Myfile = file.openread ("Foo.txt"))  {    myfile.read (buffer, 0,    } 

With the using statement in the example above, you can be sure that the Myfile.dispose () method will be called immediately after the file is used, regardless of whether the read () method throws an exception.

common error   #9: Avoidance exception

C # also enforces type checking at run time. C # This allows you to find the wrong place faster than a language like C + + that assigns a random value to the wrong type conversion. However, the programmer once again ignores this feature of C #. Because C # provides two types of checks, one throws an exception and the other does not, which is likely to cause them to fall into the pit. Some programmers tend to avoid exceptions, and think that writing Try/catch statements can save some code.

For example, the following shows two different ways to display type conversions in C #:

   //  methods  1:  //  if  account   cannot be converted to  SavingAccount  throws an exception   SavingsAccount savingsAccount =  ( SavingsAccount) account;    //  Method  2:  //  If it cannot be converted, no exception is thrown, instead it returns  null  savingsaccount savingsaccount = account as savingsaccount; 

 

It is clear that if you do not judge the results returned by Method 2, it is likely that a  nullreferenceexception exception will eventually be generated, which may occur later in the day, making the problem more difficult to track. In contrast, Method 1 throws a  invalidcastexceptionmaking immediately, so the root cause of the problem is obvious.

In addition, even if you know how to judge the return value of Method 2, if you find that the value is empty, what do you do next? Is it appropriate to report an error in this method? If the type conversion fails, do you have any other ways to try it? If not, then throwing the exception is the only correct choice, and the throw point of the exception is as close as possible to the point at which it occurred.

The following example shows a common set of methods, one that throws an exception, and another that does not:

   int. Parse ();     //  throws an exception   int if the argument cannot be resolved. TryParse ();  //  returns a bool value indicating whether the resolution was successful     ienumerable.first ();            //  throws an exception   ienumerable.firstordefault () if the sequence is empty;   //  returns the  null  or default value 

&NBSP If the sequence is empty;

Some programmers think "abnormal", so they naturally think that the program that does not throw an exception appears more " Tall on. " Although this view is correct in some cases, this view does not apply to all situations.

For example, in some cases when an exception occurs, you have another option (such as the default), so choosing a method that does not throw an exception is a good choice. In this case, you are most probably writing the following:

   if  (int. TryParse (Mystring, out myint))  {    // use myint  }  else {    // use default value  } 

 

Instead of this:

   try {    myint = int. Parse (myString);    // use myint  } catch  (FormatException)  {    // use default value  } 

 

However, this does not mean The TryParse method is better. In some cases, it may not be appropriate in some cases. That's why there are two ways to choose. Choose the right method according to your situation and remember that, as a developer, exceptions can be entirely your friends.

common error #10: Cumulative compiler warning without processing

This error is not unique to C #, but in C # It's a lot more, especially since the C # compiler deprecated strict After the type check. The

Warning appears for a reason. All C # Compilation errors indicate that your code is flawed, as well as some warnings. The difference between the two is that, for a warning, the compiler can follow your code's instructions, but the compiler discovers that your code has a small problem and it is likely that your code will not work as you expect. A common example of

is that you have modified your code and removed the use of certain variables, but you forgot to remove the declaration of that variable. The program can run well, but the compiler will prompt for unused variables. The program works well so that some programmers don't fix the warnings. What's more, some programmers take advantage of the hidden warning feature of the Error List window in Visual Studio and easily filter out warnings to focus on errors. Without too much time, you accumulate a bunch of warnings that are "comfortable" (and, worse, hidden).

However, if you ignore this kind of warning, similar to the following example will appear in your code sooner or later.

   class Account {        int myId; The       int Id;   //  compiler has warned you, but you don't listen to          // constructor      account (Int id)  {          this.myId = Id;      // oops!      }    } 

 

Coupled with the IntelliSense features of the editor, this error is likely to occur.

Now that you have a serious error in your code (but the compiler just output a warning for reasons that have been explained), it will waste a lot of your time looking for this error, depending on how complex your program is. If you notice this warning from the beginning, you can change it in just 5 seconds to avoid this problem.

Remember, if you look closely, you'll find that the C # compiler gives you a lot of useful information about your program's robustness. Do not ignore the warning. you can fix them in a few seconds and fix them when they appear, which can save you a lot of time. Try to develop a "neat freak" for yourself, and let visual Studio "error window" always show "0 error, 0 warning", and if you get a warning, feel uncomfortable and immediately fix the warning.

Of course, there are exceptions to any rule. So, sometimes, although your code seems a bit problematic in the compiler, it's just what you want. In this rare case, you'd better use the #pragma warning disable [warning ID] to wrap the code that raises the warning, and only package the code that corresponds to the warning ID. This will only suppress the corresponding warning, so you will know when a new warning is generated.

Summarize

C # is a powerful and flexible language that has many mechanisms and language specifications to significantly improve your productivity. As with other languages, if you have limited knowledge of its capabilities, it is likely to hinder you, not the benefits. As the saying goes, "knowing enough to being dangerous" (the translator notes that it is self-sufficient to know enough to do something, but it is not).

Familiarity with some of the key nuances of C #, like those mentioned in this article (but not limited to these), can help us to better use the language and avoid some common pitfalls.

The 10 most common mistakes a C # programmer makes

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.