Here's some things I came up with so far:

1. Use proper Dispose pattern to properly release resources (such as raw pointers or handles, files or network connections…):

Code:
class SomeClass : BaseClass, IDisposable
{
    SomeManagedResource   _managedResource   = null;
    SomeUnmanagedResource _unmanagedResource = null;
    bool _disposed = false;

    public void Dispose() 
    {
        Dispose( true );
        GC.SuppressFinalize( this );      
    }

    protected virtual void Dispose( bool disposing )
    {
        // If you need thread safety, use a lock around these  
        // operations, as well as in your methods that use the resource. 
        if ( !_disposed )
        {
            if ( disposing )
            {
                // Clean up managed resources here.
                if ( _managedResource != null )
                {
                    _managedResource.Dispose();
                }
            }

            _managedResource = null;

            // Clean up unmanaged resources here.
            if ( _unmanagedResource != null )
            {
                ReleaseHandle( _unmanagedResource );
                _unmanagedResource = null;
            }

            // Always call Dispose() on all disposable base classes.
            base.Dispose( disposing );

            // Indicate that the instance has been disposed.
            _disposed = true;
        }
    }

    ~SomeClass()
    {
        Dispose( false );
    }
}

2. Call the Dispose() method of all classes that implement IDisposable in try/finally blocks or in ‘using’ statements rather than waiting for the garbage collector to do it for you because then it will free up those resources sooner and garbage collection may happen a long time later (if ever).

3. Don’t use Sleep() as a hack! Minimize the number of places you use it and only sleep for the shortest time necessary. An example of using Sleep() as a hack is when you launch an asynchronous process and you want to wait until the process is finished before continuing. You determine that the process takes about 10 seconds to finish, so you add Sleep( 15000 ) and then continue the rest of the program.
This is very sloppy and dangerous because there are many things that could cause the process to take longer than 15 seconds, in which case your program would fail because it’s assuming the process is finished. It also wastes time because on some systems the process may only take 5 seconds to finish, in which case you’re doing nothing for 10 extra seconds. A classic symptom of programs that use Sleep hacks is intermittent “flaky” failures.
The proper way to handle a situation like this is to use a loop and only sleep for a small interval each iteration (such as 100ms) then check a condition that would indicate whether the process is complete, then break out of the loop.

4. Don’t hard-code configuration data. Put things like paths & filenames, hostnames, port numbers, timeout values, usernames/passwords, logging levels… into separate config files or a common file included by all your source files. This way it’s easier to modify those values later since they’re all in one place. The only hard-coded strings you should put in your code are things that won’t need to change based on different configurations, such as error messages.

5. Use System.IO.Path.DirectorySeparatorChar instead of hard-coding a ‘\’ or ‘/’ in directory paths; and use relative paths instead of absolute paths whenever possible. This helps keep your code portable between Windows & UNIX.