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.