This article will be updated every time I run into a best practice.

Checklist Microsoft Visual Studio 2008 Solution and Projects
– Solution name should be Customer.Product.sln, like MyCustomer.MyProduct.sln
– Project name should be Customer.Product.Subsystem.csproj, like MyCustomer.MyProduct.MySubsystem.csproj
– Setup project name should be Customer.Product.Subsystem.Setup.csproj, like MyCustomer.MyProduct.MySubsystem.Setup.csproj
– Test project name should be Customer.Product.Test.csproj, like MyCustomer.MyProduct.Test.csproj
– Create 1 C# project per subsystem and 1 folder per layer, like BC, BE, Dac, Common etc.
– Create 1 Setup project per subsystem
– Create 1 Test C# project per solution and 1 folder per subsystem in the test C# project
– For each project check the .NET version you want to build against (Target Platform)
– Enable “Check for arithmetic overflow/underflow” on debug build
– Disable “Check for arithmetic overflow/underflow” on release build

Checklist C# Projects
– Use String.Format to concatenate strings < 5
– Use StringBuilder to concatenate strings >=5
– Never use relative paths, because if an executable is called by an other executable or batch file located in an other folder the working directory is not the location of the executable.
  Always use

        public string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }

Or as property

        private static string _assemblyDirectory = null;
        public static string AssemblyDirectory
        {
            get
            {
                if(_assemblyDirectory == null)
                {
                    var codeBase = Assembly.GetExecutingAssembly().CodeBase;
                    var uri = new UriBuilder(codeBase);
                    var path = Uri.UnescapeDataString(uri.Path);
                    _assemblyDirectory = Path.GetDirectoryName(path);
                }

                return _assemblyDirectory;
            }
        }

For difference Assembly.CodeBase en Assembly.Location, see msdn

Checklist Setup Projects

– Before editing anything on a setup project, check out the setup project by hand

– Change Author

– Change Description

– Change DetectNewerInstalledVersion to True

– Change InstallAllUsers to True

– Change Manufacturer

– Change ManufacturerUrl

– Change ProductName

– Change RemovePreviousVersions to True

– Change Subject

– Change SupportPhone

– Change SupportUrl

– Change Title

– Change Version

– Change Application Folder to “[ProgramFilesFolder][Manufacturer]\ProductName\[ProductName]”

– If you want to change the for “AddRemoveProgramsIcon” icon of the setup project:

           > Add a icon file to the primary output C# project

           > Open the C# project properties “Application” page

           > Select the icon file in the “Icon and manifest” dropdownbox

           > In the setup property grid select browse… > click on browse in icon dialog > select “All Files (*.*)” on the “Files of type” dropdownbox primairy > select primary output from “you’re C# project” as source for “AddRemoveProgramsIcon”

Don’t use a icon selected directly from filesystem, because you can get sourcecontrol binding problems for you’re setup project, unless it is added to the project it self in the same directory as de setup project file.

Switch

Every switch statement should have it’s own function.

Functions

In most cases a method should be on the object whose data it uses.

Unittest

Every class should have it’s own unittest class.

Every method should have at least one unittest (happy path)

Unittesttemplate:

using System;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

namespace Customer1.Product1.Test.Subsystem1.Bc
{ [TestFixture] public class DateTimeTester { [TestFixtureSetUp] public void FixtureSetUp() { Console.WriteLine("This code is executed before the first test in this class is executed"); } [TestFixtureTearDown] public void FixtureTeardown() { Console.WriteLine("This code is executed after the last test in this class is executed"); } [SetUp] public void Setup() { Console.WriteLine("This code is executed before the start of every test in this class"); } [TearDown] public void Teardown() { Console.WriteLine("This code is executed after the end of every test in this class"); } [Test] public void AddDays_HappyPath_AddedOneDayToDateTime() { // Arrange DateTime currentDateTime = new DateTime(2009,7,1); DateTime expectedDateTime = new DateTime(2009, 7, 2); // Act DateTime addedDateTime = currentDateTime.AddDays(1); // Assert Assert.That(addedDateTime, Is.EqualTo(expectedDateTime)); } } }

100% Coverage, even with read-only properties

If you have to write a unit test for properties that are read-only, like the  System.Configuration.ConfigurationManager.ConnectionStrings["Database1"
you can create a method with the read-only property as parameter. By doing that, you can have 100% coverage.

// Get connectionstring from App.config
string connectionString = GetConnectionString(ConfigurationManager.ConnectionStrings["Database1"])

public static string GetConnectionString(ConnectionStringSettings section)
       {
           if (section == null)
           {
               throw new NullReferenceException(@"No xmlnode <configuration>...<connectionStrings><add name=""Database1""... in App.config");
           }
           if (string.IsNullOrEmpty(section.ConnectionString))
           {
               throw new NullReferenceException(@"Xmlnode attribute <configuration>...<connectionStrings><add name=""Database1"" connectionString=""... was empty");
           }
           return ConfigurationManager.ConnectionStrings["Database1"].ConnectionString;
       }

Logging

If you want to log all the items in an string array, you can use the string.Join function:

string[] args = {"item1", "item2", "item3"};

Console.WriteLine(string.Join(",",args));

Global Exception Handling in ASP .NET

As a good programmer, you normally want to catch all exceptions at the highest tier in code so that your program has the chance to display the error message to the user or log it to the appropriate location. Under ASP.NET, you can do so easily by overriding the Application_Error event in Global.asax file.

protected void Application_Error(object sender, EventArgs e)
{
    Exception ex = Server.GetLastError();

    // Perform error logging here...

    // Clear the error and maybe redirect to some other page...
    Server.ClearError();
}

Global Exception Handling in a .NET console application or Windows Service

Unfortunately, there is no concept of Global.asax in a winform or console application in .NET. In a windows form application, the equivalent global catch all is available by listening to the ThreadException and AppDomain UnhandledException events.

class Program
    {
        [STAThread]
        static void Main()
        {
            // Catch all unhandled exceptions in all threads.
            AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
        }
        private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
        {
            try
            {
                // Log error here or prompt user...
            }
            catch { }
        }
    }

Global Exception Handling in a .NET windows application

class Program
    {
        [STAThread]
        static void Main()
        {
            // Catch all unhandled exceptions
            System.Windows.Forms.Application.ThreadException += ThreadExceptionHandler;

            // Catch all unhandled exceptions in all threads.
            AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;

            System.Windows.Forms.Application.Run(new MainForm());
        }
        private static void ThreadExceptionHandler(object sender, System.Threading.ThreadExceptionEventArgs args)
        {
            try
            {
                // Log error here or prompt user...
            }
            catch { }
        }
        private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
        {
            try
            {
                // Log error here or prompt user...
            }
            catch { }
        }
    }

Information found at http://www.cubiczone.com/Articles/tabid/65/EntryID/24/Default.aspx

TypeOf

If you need to get the metadata of a type, use the typeof C# operator:

Console.WriteLine(typeof(string).FullName);

Chaining Constructors

Using chaining constructors if you define multiple constructors that use the (partial) same logic.

Constant and Read-Only fields

Use constant fields for fields that contain constant data that is know at compile time. Use read-only fields for fields that contain constant data that is not know at compile time and can be set in the constructor

The Is keyword

Use the Is keyword to check for correct type

Use thow; to rethrow an exception

This will preserve the original context of the exceptioin

Default

Use the default keyword to get the default value for a type even generic types:

var result = default(T);

Garbage Collection

Don’t call the garbage collection from code, but if you have to (because you are managing a very large array or something), call garbage collection as follow:

GC.Collect();

GC.WaitForPendingFinalizers();

Never use destructors or finalize methods, unless you have to, because you are using unmanaged code, because declaring destructors or finalize methods marks the object as finalizable and stores a pointer on the finalization que and then there is a whole story to tell,  but the point is, it is slower.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.