Showing posts with label Performance. Show all posts
Showing posts with label Performance. Show all posts

Wednesday, 1 February 2012

Performance in ASP.NET and ADO.NET


DataReaders, DataSets, Connection Pooling, Cache and SP’s
·        Connection Pooling in ADO.NET;
·        Reading DB data using DataReaders;
·        Using memory DataSets;
·        Performance with Stored Procedures.
·        C#, Visual Studio 2003, ADO.NET, ASP.NET.


In this article I will highlight some interesting tips to optimize ASP.NET applications. We’ll get acquainted with the powerful resources of data cache, practical use of stored procedures, connection pooling and other advanced techniques. You’ll learn how to use DataSets in memory to avoid unnecessary queries to the SQL server and, therefore, optimize data traffic. You’ll also be introduced to the interesting ADO.NET connection pooling resource.
To build the examples, I’ll use Visual Studio .NET 2003 and the SQL Server 2000 as database. The applications will be made using C#, but they can easily be written in VB.NET, in case you want to use that language.

Connection Pooling
In Visual Studio, click File>New>Project (or press Shift+Ctrl+N) and in the New Project window choose ASP.NET Web Application in the item Visual C# Projects. In the Location option, name the application and then click OK.
Starting from ToolBox, places a SqlConnection in the Web Form. Select the component and in the Properties window select New Connection in the property ConnectionString. In the editor that appears inform server IP address or server name in the first entry box. In User name and Password inform standard user and password for database access. And, finally, choose the Northwind database and click Ok. If you want, click the Test Connection button to check if the parameters are correct. By doing this, we setup the SQL Server connection using SQL provider, the first performance tip (never use OleDB or ODBC in this case).
Notice that in User name and Password we inform a standard database access user and password, but could have used integrated authentication. However, here comes the second valuable tip for optimization: supply a fixed user and password, in such a way that all users that connect to the application use the same credentials. If it’s necessary to restrain a certain user’s access, define that in Web.Config authorizations. Supplying a fixed user will make ADO.NET use the Connection Pooling resource effectively, without having performance loss.
Connection Pooling is the mechanism that allows ADO.NET to reuse database connections. Picture the following situation: a user access the application, we connect to the database to extract information and show it in the form. After that, we end the connection and feedback the result to the browser. Since Web applications are state-less, if this exact same user or another connects to the application, a new connection with database will need to be reestablished. Connecting to the database at each user’s request is literally a suicide in Web environment, where an application can have hundreds and even thousands of simultaneous connections.
The ADO.NET solves this in a sufficiently elegant manner: after the page is sent to the browser, the connection with database isn’t released, even if you’ve explicitly called SqlConnection close method. The ADO.NET automatically saves the connection in pool (picture this as a kind of connections cache). That is, the connection stays open with the database and persists between requests. When another user connects to the application, the ADO.NET checks if there exists an available connection in pool and in case it finds one, uses it. With this, all the time needed to locate the database server, establish a connection, authenticate a user and check permissions will no longer be taken at each request.
And the best part of it all, you don’t need to do anything to use this resource, since it’s already activated by default. Creating a Connection Pooling mechanism manually through code is something extremely complicated (unfortunately, I’ve had to got through that effort in a given point in time). In ADO.NET, we’ve already got that ready in the framework itself. Productivity is one of the strong points of .NET.

Note: Connection Pooling can only be used in a multi-thread environment (a Web application, for example), where we have several simultaneous threads processing client requests. It makes no sense, for example, use Connection Pooling in a traditional Windows Forms application (two layers). Internally, Connection Pooling uses an interesting resource of the Operational System to provide its functionality: semaphores

You can even control how ADO.NET works with Connection Pooling, making some adjustments in SqlConnection’s ConnectionString property. We can specify some parameters, see the main ones in Table 1.


Maximum number of connections that can stay in pool.
Minimum number of connections that can stay in pool.
Indicates if Connection Pooling is registered.
Table 1. Connection Pooling Parameters
Observe an example of how we could use some of these parameters in our application’s connection string (you can make this change straight in the ConnectionString property, starting from the Properties window):

workstation id=ASUS;packet size=4096;user id=sa;data source=ASUS;persist security info=False;initial catalog=Northwind;Pooling=True;Min Pool Size=50;Connection Lifetime=120;

Attention: always use the same ConnectionString (with the same values for all parameters) in all connection objects, so that they share the same Connection Pooling.
Using a DataReader to display data in a DataGrid
Following up on our example, first we’ll see how to display data from a given SQL Server table in a DataGrid control, using our previously configured connection.
The fastest way to extract data from a database is using a DataReader (a SqlDataReader, in case of the SQL Server provider). A DataReader is responsible for the reading of the data returned for a SQL query, using a data cursor. It is fast for the following reasons:
·        It is unidirectional: navigation through records is made in a sequential fashion (forward-only). Basically, we read a record to do something with it (display data in a control, for instance) and navigate to the next record;
·        It is read-only: it isn’t possible to modify a DataReader;
·        It doesn’t make cache: after a record is read, it is discarded from the memory.
The code in Listing 1 shows how to use a DataReader (place the code in the Web Form’s Page Load). Here we establish the connection to the database using SqlConnection’s Open method. After that, we use a SqlCommand to execute a Select command in the Products table. To run the query, we call upon the ExecuteReader method of SqlCommand (here called cmd), which return value is attributed to DataGrid’s DataSource property.

using System.Data.SqlClient;
...
private void Page_Load(object sender, System.EventArgs e)
{
    sqlConnection1.Open();
    try
    {
        SqlCommand cmd = new SqlCommand("select * from Products",sqlConnection1);
       DataGrid1.DataSource = cmd.ExecuteReader();
       DataGrid1.DataBind();                  
    }
    finally
    {
       sqlConnection1.Close();
    }
}

Listing 1. Using the DataReader
But where it is the object SqlDataReader? A DataReader is never instanced directly. You will always get the reference to an object of this type through the calling upon a SqlCommand’s ExecuteReader method. You can then use the return object to sweep the obtained resultset, using its Read method. Here it wasn’t necessary to do this sweeping manually, since we used the DataBind resource of the ASP.NET for data connection with DataGrid, through the DataSource property.
Notice that all the execution code is wrapped in a try...finally block. The code inside the finally block will always be executed, unconditionally, even if an exception occurs (for example, if the command SQL typed wasn’t valid). With this we make sure that SqlConnection’s Close is always called after the execution of the code inside try, returning the connection to the pool. Another tip is to always keep the minimum code possible between Open and Close, only what makes use of the open connection.
Using DataSets in Cache
Without a doubt the main component in ADO.NET is DataSet. It can be used with any of ADO.NET providers, representing a data structure in memory. Here the term “memory” relates to the fact that we can create a query to a database table, extract information and use them after the connection is closed. For this reason, applications that use this component are known as “disconnected”.
While a DataReader demands that a connection is active so that a data reading is made, a DataSet can get its data once and keep them in memory for posterior use. This assures even more the scalability of ASP.NET applications. It’s up to you to detect when it is better to use a DataReader or a DataSet.
To be clearer, we’ll imagine a scenario: you work for a university and need to build the registration page for that institution’s application forms. In this page, there’s a form for candidate’s register, where there are some TextBoxes for the filling in of fields such as Name, Address, Date of Birth, etc. The only dynamic information (that comes from a database) is in a DropDownList, where the candidate can choose the course to which he wishes to subscribe to. This information is obtained from a database table, that contains all the courses with available vacant.
Now answer one thing: why connect to the database to fill in the DropDownList, every time a user opens the page, if the courses never change? In a situation such as this, the courses’ table would be modified in the database probably once a year or semester. This is a typical example of where we can use a DataSet instead of a DataReader.
Let’s see how to use the resource in the practice. Start a new ASP.NET application, following the same steps of the previous example. Place a SqlDataAdapter in the form and, in the wizard that’ll open, chose the connection that we’ve created previously. Use the following SQL instruction to get data from the Categories table (that stores information of product’s categories):

SELECT
    CategoryId, CategoryName
FROM
    Categories

Place a DropDownList on the form and on the Page_Load of the Web Form type the code presented in List 2.

private void Page_Load(object sender, System.EventArgs e)
{
    if (!IsPostBack)
    {
        DropDownList1.DataSource = dsCatProd();
        DropDownList1.DataMember = "Categories";
        DropDownList1.DataTextField = "CategoryName";
        DropDownList1.DataValueField = "CategoryId";
        DropDownList1.DataBind();      
    }
}
Listing 2. Page_Load event code
Here, we are simply testing if the page is being loaded for the first time (IsPostBack) to then initialize the DropDownList. Observe that we’ve attributed the value of the DataSource property for a function called dsCatProd (seen in Listing 3), that returns a DataSet. The dsCatProd function as shown in Listing 3.

private DataSet dsCatProd()
{
    if (Cache["dsCatProd"] == null)
    {
        DataSet ds = new DataSet();
       sqlDataAdapter1.Fill(ds,"CATEGORIES");
       Cache["dsCatProd"] = ds;
    }
    return (DataSet)Cache["dsCatProd"];
}
Listing 3. dsCatProd Function
In the previous code, we tested if a variable with the name we defined (“dsCatProd”) exists in the Cache. If it does exist, it’s because DataSet is already in memory (a user has probably already made the query previously). If not, we’ll remake the data cache, by calling the SqlDataAdapter’s Fill method to fill the DataSet’s data. And, finally, we place DataSet in memory (Cache object).
The Cache object is used in ASP.NET to share data between all of the application’s users. Even after the request is processed, the information remains in the server memory and can be used later. You can place as many objects as you wish in memory, needing only to supply a different name for each one, but be careful not to exaggerate in the amount of information that you store in the server.

Note: ASP.NET allows you to use a state server, allowing the cache stored information and session to reside in a process different other than the aspnet_wp.exe (the process used by the ASP.NET to run Web applications). It is also possible to specify a dedicated server (that has more memory, for example) only to store cache and session objects. Another alternative is to persist the sessions in the database itself, to save memory. State Servers are also used to share session data when multiple servers are used.

Figure 1 shows the application running. Make a test: open a second browser and access the same page however, stop the database server before. Observe that data will be shown even if the SQL Server is disabled; indicating that the information really hadn’t been obtained from the database, but from the cache that was already stored in the server memory.

image001.png
Figure 1
. Using a cached DataSet


Using DataViews
Let’s improve the example. We’ll allow the user to see the related products when he chooses a certain category. Instead of creating a new query to the database after the selection, we’ll filter a query that will already be resident in memory. That is, we’ll keep all the products in cache and use a DataView for filtering.
Place a ListBox in the form and a second SqlDataAdapter, configuring the following SQL instruction, which returns all the records from the table of products (Products):

SELECT
    CategoryId, ProductName
FROM
    Products

Instead of using a second DataSet to store the products, we’ll use the same created previously. This is possible in ADO.NET; all that needs to be done is pass the same DataSet as a parameter for the Fill method of both the SqlDataAdapters. Include then the following code, just below the Fill of the SqlDataAdapter1, which is in dsCatProd method:

...
sqlDataAdapter2.Fill(ds,"PRODUCTS");

Now, when the page is opened for the first time, DataSet will contain in memory data on all the products and categories of the database. All we need to do is manipulate this information in the best way, without the necessity of querying the database at every request. For example, to display the products related to the selected category, modify Page_Load as shown in Listing 4.

private void Page_Load(object sender, System.EventArgs e)
{
    if (!IsPostBack)
    {
        // DropDownList code
    }
    else
    {
        ListBox1.DataSource = dvCatProd(DropDownList1.SelectedValue);
        ListBox1.DataTextField = "ProductName";
        ListBox1.DataBind();
    }
}
Listing 4. Updated Page_Load event code
The code is similar to the used one to fill in the DropDownList, except that now we are using a DataView as data source for the ListBox control. The dvCatProd function receives the code from a category as a parameter and returns the related products, through a DataView, obtained from the memory DataSet. Its code is shown in Listing 5

private DataView dvCatProd(object CategoryId)
{
    DataView dv = new DataView();
    dv.Table = ((DataSet)Cache["dsCatProd"]).Tables["PRODUCTS"];
    dv.RowFilter = "CategoryId = " + CategoryId.ToString();
    return dv;
}
Listing 5. dvCatProd Function
A DataView is ideal to be used with memory DataSets. It allows you to filter and organize resultsets, supplying different visions of a same data set, without using any kind of SQL instruction or extra communication with the database. It is worth remembering that you can have several DataViews acting the same DataSet.
The last step is to setup AutoPostBack of DropDownList to True, so that the postback is made in the server when the user chooses an item from the control. Figure 2 shows the example in execution.

image003.png
Figure 2
. Using DataViews from a DataSet in memory
It is clear, if the data is modified in the database, they will not be reflected in the DataSet of cache. It is your job to remake the cache whenever necessary. ASP.NET possesses some resources to make this process easier. We can, for example, specify a cache expiration criterion, or create a dependence mechanism.
Stored Procedures
We could not speak of ASP.NET applications optimizations with ADO.NET without showing the use of Stored Procedures. They drastically increase the speed of Web solutions, as much in the obtaining of data from the SQL Server as in the update, insertion or exclusion of records. This happens because SQL server can optimize execution plans and pre-compile these instructions that reside in the database, and no longer in the customer application, which only takes charge of passing the appropriate parameters.
In this example, we will build a ASP.NET application that will make effective use of Stored Procedures, in such a way as to demonstrate the increase in scalability. As is custom, you’ll learn how Visual Studio can help us in this process, automatically creating the procedures in SQL Server through wizards of SqlDataAdapter.
In a new ASP.NET application, we will put some TextBoxes for data entry in the Web Form. To make this easier, we will use only the table’s main fields (ProductName, CategoryID and UnitPrice) and we will work only with the addition operation. The names (ID property) of the TextBoxes must be as tbProductName, tbCategoryID and tbUnitPrice, respectively. Put some Labels and setup the Text to indicate what be must filled in. A Button with the Text “Insert Product” will be used to add the information in the database. Use Figure 3 as a reference to setup your form.

image005.png
Figure 3.
Main form of the application, with TextBoxes
The first step is to place a SqlDataAdapter from the Toolbox. An wizard will be showed, which will help in the setup of the various component options. Click in Next and in the screen that is displayed, click the New Connection button. Fill in the connection information, informing the SQL server name, user/password and database. Click in Test Connection to see if everything is working properly. Next, click OK and, back to the wizard, click Next.
The next screen is very important in the SqlDataAdapter setup process. This is the moment when we have to inform VS.NET how the component will gain access to the database: using standard SQL commands (Select, Update, Delete and Insert) or through Stored Procedures. Since we haven’t yet created the procedures in the database, we’ll ask VS.NET to do that automatically for us, by selecting the last option. Next, click Next.
In the following screen, we have to setup the query to be used by SqlDataAdapter in order to obtain SQL Server data. To assist in the creation of the query, we can use the Query Builder option, which will open a visual editor. In the Add Table window, double click Products and, after that, click Close. In the Query Builder, select the fields ProductID, ProductName, CategoryID and UnitPrice (the others will not be used in this example). Observe that the Select command is being generated automatically by the IDE. Based on Select, the IDE will then generate the codes of the procedures. Click Ok to confirm and then Next.
In the next screen, we can inform the names that will be given to the new procedures to be created in the SQL Server. Use the standard that you desire, here I called them “Upd_Product”, “Ins_Product” and so forth. In this same window, observe that we have the option “Yes, create them in the database for me”, indicating that the IDE can create the procedures in the database.
Clicking in Preview SQL Script, we can visualize the DDL code generated by the creation of the Stored Procedures. Observe that each procedure receives entry parameters, which are used to complement the SQL instructions that they use (Insert, Update, Delete and Select).
Click again in Next and check that in the last screen of the wizard a summary is displayed, indicating the actions to be made by the IDE. Click Finish to confirm the operations. At this moment, the IDE performed a series of operations for us, they are:
1 – Creation of four Stored Procedures in the database called, respectively, Sel_Products, Upd_Products, Ins_Products and Del_Products;
2 – Addition of two components to the designer of the application’s main Web Form, one SqlConnection and one SqlDataAdapter;
3 – Initializing of the SqlDataAdpater’s internal SqlCommands (DeleteCommand, InsertCommand, SelectCommand and UpdateCommand properties);
4 – For each internal SqlCommand described in the prior step, the IDE did the following:
·        Reference to the SelConnection1connection through the Connection property;
·        Setup CommandType for StoredProcedure (if there were traditional SQL Statements, this property would be setup as Text);
·        Set the CommandText property to the respective Stored Procedure created in the SQL Server;
·        Initialize the necessary parameters in the collection Parameteres.

The last step is to codify the Click event of the WebForm Button so that it executes the Stored Procedure and inserts the data from the form in the database. All we need to do is to capture the values of the TextBoxes and use them to fill the SqlDataAdapter InsertCommand parameters. The code of Listing 6 shows how to do this.

private void Button1_Click(object sender, System.EventArgs e)
{
       SqlCommand cmd = sqlDataAdapter1.InsertCommand;
       cmd.Parameters["@ProductName"].Value = tbProductName.Text;
       cmd.Parameters["@CategoryId"].Value = tbCategoryID.Text;
       cmd.Parameters["@UnitPrice"].Value = tbUnitPrice.Text;
       try
       {
             cmd.Connection.Open();
             cmd.ExecuteNonQuery();                        
       }
       finally
       {
             cmd.Connection.Close();
       }
}
Listing 6. Configuring the insertion using Stored Procedures
Remember to add the namespace System.Data.SqlClient in using. Run the application to test the insertion from the ASP.NET form. Inform the values in the TextBoxes and click in the button Insert Product to insert the data in the SQL server using the Stored Procedure created. Select the table and observe that the register was included successfully.
Obtaining data using Stored Procedures
In this second example, we will see how to create and gain access to a Stored Procedure that returns a data set, showing data in DataGrids. Our Stored Procedure will be special: it will return two ResultSets, in such a way as to optimize the solution, containing data from the tables of Categories and Products.
Instead of using the IDE wizard to create the procedure, we will use the Enterprise Manager of SQL Server. In the Northwind database, locate the item Stored Procedures and right click on it, choosing the New Stored Procedure option (name it “Sel_ProductsAndCategories”). Click the Check Sintax button to verify your typing. Listing 7 shows the script.

CREATE PROCEDURE dbo.Sel_ProductsAndCategories
AS
       SET NOCOUNT ON;
SELECT * FROM Categories;
SELECT * FROM Products;
GO
Listing 7. Stored Procedure Code Sel_ProductsCategories
In VS.NET, start a new ASP.NET application. Place a SqlConnection and setup the ConnectionString to access the Northwind database. Place also a SqlCommand and set your Connection to SqlConnection1. Set your CommandType to value Stored Procedure and in CommandText type Sel_ProductsAndCategories. And, finally, place two DataGrids.
In the Page_Load type the code in Listing 8. In the code, we call the ExecuteReader method of the SqlCommand to run the Stored Procedure, instead of the ExecuteNoQuery. This because the Stored Procedure returns a result set (actually, two), that it is returned in a SqlDataReader object (remember to add the System.Data.SqlClient in using).
Next, we make the DataBind of the DataReader for the first DataGrid, that will show data from the Categories table. In order for the SqlDataReader to advance to the second ResultSet, we call its NextResult method. We then make the DataBind for the second DataGrid, that will show data from the Products table.

private void Page_Load(object sender, System.EventArgs e)
{
       sqlConnection1.Open();
       try
       {
             SqlDataReader rd = sqlCommand1.ExecuteReader();
             DataGrid1.DataSource = rd;
             DataGrid1.DataBind();
             rd.NextResult();
             DataGrid2.DataSource = rd;
             DataGrid2.DataBind();            

       }
       finally
       {
             rd.Close();
               sqlConnection1.Close();
       }                  
}
Listing 8. Using a SqlDataReader to extract data from the database through Stored Procedures
 With this, our solution is already sufficiently optimized, since we are making use of DataReaders (the fastest way to read data from the database) along with Stored Procedures. Observe, also, that bringing two ResultSets in this, makes the result a lot faster if compared to an application that brings data from two tables using two Selects.
Conclusions
Using the techniques seen in this article, you will now be able to optimize your ASP.NET applications so that they have a better performance and scalability, leaving their final users much more satisfied with their response time. The use of Stored Procedures, when done in an appropriate manner, can assure the success of its ASP.NET solution with ADO.NET, being unbeatable if compared to any another existing technology. DataSets in memory and DataReaders also are excellent ingredients to turbo your Web Site with ASP.NET and ADO.NET.

Wednesday, 25 January 2012

Writing code C# Performace

Writing code C# Performace

Writing code that runs quickly is sometimes at odds with writing code quickly. C.A.R. Hoare, computer science luminary and discoverer of the QuickSort algorithm, famously proclaimed, "Premature optimization is the root of all evil." The extreme programming design principle of "You Aren't Gonna Need It" (YAGNI) argues against implementing any features, including performance optimizations, until they're needed.
Writing unnecessary code is undoubtedly bad for work efficiency. However, it's important to realize that different situations have different needs. Code for vehicular real-time control systems has inherent up-front responsibilities for stability and performance that aren't present in, say, a small one-off departmental application. Therefore, it's more important in such code to optimize early and often.
Performance tuning for real-world applications often involves activities geared towards finding bottlenecks: in code, in the network transport layer, and at transaction boundaries. However, these techniques alone cannot solve the dreaded problem of uniformly slow code, which surfaces when large bottlenecks have been resolved but the code still exhibits inadequate performance. This is code that has been written without attention to correct usage, often by junior programmers, in the same style across whole modules or applications. Unfortunately, the best solution for this problem is to make sure that all programmers on a project follow correct coding practice when writing the code the first time; coding guidelines and good shared libraries help enormously.
This article presents helpful tips for writing in-process .NET managed code that performs well. It's assumed that basic programming skills such as factoring control structures, pulling work outside of loops whenever possible, caching variables for reuse, use of the switch statement, and the like are known to the average reader.
All code examples referred to in this article can be downloaded from the .NET Developer's Journal Web site, at www.sys-con.com/dotnet/sourcec.cfm. The code comes with a Windows Forms application that can be used to easily view the code and run all the tests on your own machine. You'll need the .NET Runtime 1.1 to run the code.
Tools
While testing tools such as NUnit and the upcoming VS.NET 2005 Team System can help you find bottlenecks, when tuning small sections of code, there's still no substitute for the micro-benchmark. This is because most generic testing frameworks depend on things like delegates, attributes, and/or interface method calls to do testing, and the code usually is not written with benchmarking primarily in mind. This can be very significant if you're interested in measuring the execution time of a batch of code down to the microsecond or even nanosecond level.
A micro-benchmark consists of a tight loop isolating the code that's being tested for performance, with a time reading before and after. When the test has finished, the start time is subtracted from the end time, and this duration is divided by the number of iterations to get the per-iteration time cost. The following code shows a simple micro-benchmark construct:


int loopCount = 1000000000;
long startTime, endTime;
double nanoseconds;

startTime = DateTime.Now.Ticks * 100;
for(int x = 0; x < loopCount; x++) {
  // put the code to be tested here
}
endTime = DateTime.Now.Ticks * 100;
nanoseconds = ((double)(endTime - startTime)) / ((double)loopCount);
Console.WriteLine(nanoseconds.ToString("F") + " ns per operation");
When performing a simple micro-benchmark, it's important to remember a couple of things. First, small fluctuations (noise) are normal, so to obtain the most accurate results, each test should be run several times. In particular, the first set of tests executed after program initialization may be skewed due to the lazy acquisition of resources by the .NET runtime. Also, if your results are very inconsistent, you may not have penetrated the "noise floor" of the measurement. The best solution for this is to increase the number of loops and/or tests. Another thing to remember is that looping itself introduces overhead, and for the most accurate readings, you should subtract this from the result. On a P4-M 2-GHz laptop, the per-loop overhead for a for loop with an int counter in release mode is around 1 nanosecond.
I'd never advocate running each code fragment from a long program through micro-benchmarks, but benchmarking is a good way to become familiar with the relative costs of different types of expressions. True knowledge of the performance of your code is built on actual observations. As time goes on, you'll find yourself needing such tests less and less, and you'll keep track in the back of your head of the relative performance of the statements you're writing.
Another important tool is ildasm.exe, the IL disassembler. With it, you can inspect the IL of your release builds to see if your assumptions are correct about what's going on under the covers. IL is not hard to read for a person familiar with the .NET framework; if you're interested in learning more, I suggest starting with Serge Lidin's book on the subject.
A great free tool for decompiling IL to C# or VB source, Reflector, is found at www.aisto.com/roeder/dotnet/; it's incredibly useful for viewing code that ships with the .NET Framework, for those of you less familiar with IL.
The CLR Profiler, available as a free download from Microsoft's Web site, allows you to track memory allocation and garbage collection activity, among other useful features. Also, the MSDN Web site has excellent coverage of performance metrics tools such as WMI and performance counters.
Working with Objects and Value Types
Objects: A Double Whammy
Objects are expensive to use, partly because of the overhead involved in allocating memory from the heap (which is actually well-optimized in .NET) and partly because every created object must eventually be destroyed. The destruction of an object may take longer than its creation and initialization, especially if the class contains a custom finalization routine. Also, the garbage collector runs in an indeterministic way; there's no guarantee that an object's memory will be immediately reclaimed when it goes out of scope, and until it's collected, this wasted memory can adversely affect performance.
The Garbage Collector in a Nutshell
It's necessary to understand garbage collection to appreciate the full impact of using objects. The single most important fact to know about the garbage collector is that it divides objects into three "generations": 0, 1, and 2. Every object starts out in generation 0; if it survives (if at least one reference is maintained) long enough, it goes to 1; much later, it transitions to 2. The cost of collecting an object increases with each generation. For this reason, it's important to avoid creating unnecessary objects, and to destroy each reference as quickly as possible. The objects that are left will often be long-lived and won't be destroyed until application shutdown.
Lazy Instantiation/Initialization
The Singleton design pattern is often used to provide a single global instance of a class. Sometimes it's the case that a particular singleton won't be needed during an application run. It's generally good practice to delay the creation of any object until it's needed, unless there's a specific need to the contrary - for instance, to pre-cache slow-initializing objects such as database connections. The "double-checked locking" pattern is useful in these situations, as a way to avoid synchronization and still ensure that a needed action is only performed once. Lazy initialization is a technique that can enhance the performance of an entire application through object reduction.
Avoiding Use of Class Destructors
Class destructors (implemented as the Finalize() method in VB.NET) cause extra overhead for the garbage collector, because it must track which objects have been finalized before their memory can be reclaimed. I've never had a need for finalizers in a purely managed application.
Casting and Boxing/Unboxing Overhead
Casting is the dynamic conversion of a type at runtime to another, and boxing is the creation of a reference wrapper for a value type (unboxing being the conversion back to the wrapped value type). The overhead of both is most heavily felt in collections classes, as they all - with the exception of certain specialized ones like StringDictionary - store each value as an Object. For instance, when you store an Int32 in an ArrayList, it is first boxed (wrapped in an object) when it is inserted; each time the value is read, it is unboxed before it is returned to the calling code.
This will be fixed in the next version of .NET with the introduction of generics, but for now you can avoid it by creating strongly typed collections and by typing variables and parameters as strongly as possible. If you're unsure about whether or not boxing/unboxing is taking place, you can check the IL of your code for appearances of the box and unbox keywords.
Trusting the Garbage Collector
Programmers new to .NET sometimes worry about memory allocation to the point that they explicitly invoke System.GC.Collect(). Garbage collection is a fairly expensive process, and it usually works best when left to its own devices. The .NET garbage collection scheme can intentionally delay reclamation of objects until memory is available, and in particular longer-lived objects (those that make it to generation 1 or 2) may not be reclaimed for an extended period. Even a simple "Hello, world!" console application may allocate 15 MB or more of memory for its "working set." My advice: don't call GC.Collect() unless you really know what you're doing.
Properties, Methods, and Delegates
Avoiding Overuse of Property Getters and Setters
Most people don't realize that property getters and setters are similar to methods when it comes to overhead; it's mainly syntax that differentiates them. A non-virtual property getter or setter that contains no instructions other than the field access will be inlined by the compiler, but in many other cases, this isn't possible. You should carefully consider your use of properties; from inside a class, access fields directly (if possible), and never blindly call properties repeatedly without storing the value in a variable. All that said, this doesn't mean that you should use public fields! Example 1 demonstrates the performance of properties and field access in several common situations.
About Delegates
Delegates are slower to execute than interface methods. Delegates are often used to introduce a level of indirection in code, but in almost all cases interfaces allow a cleaner design. Of course, it's impossible to completely shun delegates; the entire event-handling paradigm in .NET is based on them. Example 2 compares the performance of delegates and direct method calls.
Minimizing Method Calls
The .NET compiler is capable of performing many optimizations for release builds. One of them is called "method inlining." If method A calls method B and certain other conditions are met, such as the code in method B being small enough, the code from B will be copied into A during compilation. However, .NET won't or can't inline certain types of methods, such as virtual methods or methods over a certain size. Each method invocation/property access entails significant overhead, such as the allocation of a stack frame, etc. Of course, you should never repeatedly call a method for the same result on purpose, but you should also be mindful of the impact of method calls in general.