~/src/www.mokhan.ca/xlgmokha [main]
cat dotnet-csharp-development-guide.md
dotnet-csharp-development-guide.md 70728 bytes | 2007-01-01 00:00
symlink: /opt/dotnet/dotnet-csharp-development-guide.md

.NET and C# Development Guide

A comprehensive guide to .NET Framework and C# development, covering language features, testing, build automation, and best practices from the early era of .NET development.

Table of Contents

  1. C# Language Fundamentals
  2. Collections and Generics
  3. ASP.NET Development
  4. Testing with NUnit and Rhino Mocks
  5. Build Automation with NAnt
  6. ADO.NET and Data Access
  7. Threading and Concurrency
  8. Component-Based Programming
  9. Development Tools
  10. Design Patterns in .NET

C# Language Fundamentals

The as Keyword for Safe Type Casting

The as keyword provides a safe way to perform type casting without throwing exceptions:

object o = new string('8', 1);

// Instead of direct casting with try/catch
try {
  string s2 = (string)o;
  Console.WriteLine("is string");
} catch(InvalidCastException) {
  Console.WriteLine("is not string");
}

// Or checking with 'is' operator (requires double cast)
if( o is string ) {
  string s2 = (string)o;
  Console.WriteLine("is string");
}

// Preferred: use 'as' keyword
string s = o as string;
Console.WriteLine((null != s) ? "is String" : "is not string");

The as operator attempts to cast to the specified type and returns null if the cast fails, avoiding expensive exception handling.

String Manipulation in C#

C# provides powerful string manipulation capabilities:

// String interpolation and formatting
string name = "World";
string greeting = $"Hello, {name}!";

// String methods
string text = "  Example Text  ";
string trimmed = text.Trim();
string upper = text.ToUpper();
bool contains = text.Contains("Example");

// StringBuilder for efficient string building
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.Append($"Item {i} ");
}
string result = sb.ToString();

Working with Generics and Default Values

When working with generic types, use default(T) to get the default value:

public T GetDefaultValue<T>() {
    return default(T);
}

// For reference types, returns null
// For value types, returns zero-equivalent

Virtual Members and Constructor Calls

Important: Calling virtual members from constructors can lead to unexpected behavior:

public class Base {
    public Base() {
        DoSomething(); // Dangerous - calls derived implementation
    }
    
    protected virtual void DoSomething() {
        // Base implementation
    }
}

public class Derived : Base {
    private string _value = "initialized";
    
    protected override void DoSomething() {
        // This runs before _value is initialized!
        Console.WriteLine(_value); // May print null/empty
    }
}

Best Practice: Avoid calling virtual members in constructors.


Collections and Generics

Choosing the Right Collection Interface

Use the most abstract interface possible for maximum flexibility:

// Instead of specific types like List<T>
public void ProcessItems(List<string> items) { }

// Use interfaces for flexibility
public void ProcessItems(ICollection<string> items) { }
public void ProcessItems(IEnumerable<string> items) { } // Most flexible

// Interface hierarchy
public interface IList<T> : ICollection<T>, IEnumerable<T> {
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);
    T this[int index] { get; set; }
}

public interface ICollection<T> : IEnumerable<T> {
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);
    int Count { get; }
    bool IsReadOnly { get; }
}

Power Collections Library

Consider using Wintellect’s Power Collections for enhanced functionality:

// Example: OrderedSet for sorted unique collections
var orderedSet = new OrderedSet<int>();
orderedSet.Add(3);
orderedSet.Add(1);
orderedSet.Add(2);
// Maintains sorted order: 1, 2, 3

ASP.NET Development

AJAX Integration

Creating AJAX call processors with ASP.NET:

public class AjaxCallProcessor : IHttpHandler {
    public void ProcessRequest(HttpContext context) {
        string action = context.Request["action"];
        
        switch (action) {
            case "getData":
                ProcessGetData(context);
                break;
            default:
                context.Response.StatusCode = 400;
                break;
        }
    }
    
    private void ProcessGetData(HttpContext context) {
        // Process AJAX request
        var data = GetDataFromDatabase();
        context.Response.ContentType = "application/json";
        context.Response.Write(SerializeToJson(data));
    }
    
    public bool IsReusable => false;
}

Avoiding Inline ASP.NET Code

Problem: Mixing server and client code in ASPX pages creates maintenance issues:

<!-- Avoid this -->
<script language="javascript">
    var serverValue = '<%= GetServerValue() %>';
</script>

Solution: Use code-behind and proper separation:

// Code-behind
protected void Page_Load(object sender, EventArgs e) {
    ClientScript.RegisterStartupScript(
        this.GetType(), 
        "serverData", 
        $"var serverValue = '{GetServerValue()}';", 
        true);
}

Testing with NUnit and Rhino Mocks

ReSharper Test Template

Standard test class template for ReSharper:

using NUnit.Framework;
using Rhino.Mocks;

namespace $NAMESPACE$ {
    [TestFixture]
    public class $CLASS$ {
        private MockRepository _mock;

        [SetUp]
        public void SetUp() {
            _mock = new MockRepository();
        }

        [TearDown]
        public void TearDown() {
            _mock.VerifyAll();
        }

        [Test]
        public void _Should() {
            // Test implementation
        }
    }
}

Mocking with Rhino Mocks

[Test]
public void Should_Calculate_Correct_Total() {
    // Arrange
    MockRepository mockery = new MockRepository();
    ICalculator calculator = mockery.Stub<ICalculator>();
    IPriceService priceService = mockery.Stub<IPriceService>();
    
    using (mockery.Record()) {
        Expect.Call(priceService.GetPrice("item1")).Return(10.0m);
        Expect.Call(calculator.Add(10.0m, 5.0m)).Return(15.0m);
    }
    
    using (mockery.Playback()) {
        var service = new OrderService(calculator, priceService);
        decimal total = service.CalculateTotal("item1", 5.0m);
        
        Assert.AreEqual(15.0m, total);
    }
}

NUnit vs MbUnit

NUnit Features:

  • Mature, stable framework
  • Simple attribute-based testing
  • Good IDE integration

MbUnit Advantages:

  • Row test attributes for parameterized tests
  • Better assertion framework
  • More flexible test organization
// MbUnit row test example
[RowTest]
[Row(1, 2, 3)]
[Row(2, 3, 5)]
[Row(-1, 1, 0)]
public void Should_Add_Numbers_Correctly(int a, int b, int expected) {
    Assert.AreEqual(expected, Calculator.Add(a, b));
}

Build Automation with NAnt

Basic Project Structure

<project name="MyProject" default="build">
  <property name="debug" value="true" />
  
  <target name="init">
    <mkdir dir="build" />
  </target>
  
  <target name="compile" depends="init">
    <csc target="library" output="build\${project::get-name()}.dll" debug="${debug}">
      <sources>
        <include name="src\app\**\*.cs" />
        <exclude name="src\app\**\AssemblyInfo.cs" />
      </sources>
    </csc>
  </target>
  
  <target name="build" depends="compile" />
</project>

Running Unit Tests

<target name="test.compile" depends="compile">
  <csc target="library" output="build\${project::get-name()}.Test.dll" debug="${debug}">
    <sources>
      <include name="src\test\**\*.cs" />
      <exclude name="src\test\**\AssemblyInfo.cs" />
    </sources>
    <references>
      <include name="build\${project::get-name()}.dll" />
      <include name="tools\nunit\bin\nunit.framework.dll" />
      <include name="tools\rhinomocks\Rhino.Mocks.dll" />
    </references>
  </csc>
</target>

<target name="test" depends="test.compile">
  <copy todir="build" flatten="true">
    <fileset basedir="tools">
      <include name="**\Rhino.Mocks.dll" />
    </fileset>
  </copy>
  <copy todir="build" flatten="true">
    <fileset basedir="tools\nunit\bin">
      <include name="*.dll" />
    </fileset>
  </copy>
  <exec basedir="tools\nunit\bin" 
        useruntimeengine="true" 
        workingdir="build" 
        program="nunit-console.exe" 
        commandline="${project::get-name()}.Test.dll /xml=${project::get-name()}.Test-Result.xml" />
</target>

Database Management

<target name="db.rebuild">
  <sql connstring="server=localhost;database=mydb;integrated security=true"
       transaction="true"
       delimiter="GO">
    <fileset>
      <include name="database\drop_tables.sql" />
      <include name="database\create_tables.sql" />
      <include name="database\populate_data.sql" />
    </fileset>
  </sql>
</target>

Using C# 3.0 Compiler without Visual Studio 2008

<property name="framework.dir" value="C:\Windows\Microsoft.NET\Framework\v3.5" />

<target name="compile">
  <csc target="exe" 
       output="MyApp.exe" 
       debug="true"
       keyfile="MyKey.snk">
    <sources>
      <include name="**/*.cs" />
    </sources>
    <references>
      <include name="${framework.dir}\System.Core.dll" />
    </references>
  </csc>
</target>

ADO.NET and Data Access

ADO.NET 2.0 Best Practices

// Use using statements for proper disposal
using (SqlConnection connection = new SqlConnection(connectionString)) {
    connection.Open();
    
    using (SqlCommand command = new SqlCommand(sql, connection)) {
        command.Parameters.AddWithValue("@param1", value1);
        
        using (SqlDataReader reader = command.ExecuteReader()) {
            while (reader.Read()) {
                // Process results
                string value = reader.GetString("ColumnName");
            }
        }
    }
}

// Use strongly-typed datasets when appropriate
DataSet dataSet = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter(command);
adapter.Fill(dataSet);

Connection String Management

// Store in configuration
string connectionString = ConfigurationManager
    .ConnectionStrings["MyDatabase"]
    .ConnectionString;

// Use connection pooling (enabled by default)
// Minimize connection lifetime
// Use parameterized queries to prevent SQL injection

Threading and Concurrency

Thread Priority Management

// Set thread priority appropriately
Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;

// For background processing
Thread backgroundThread = new Thread(ProcessData) {
    IsBackground = true,
    Priority = ThreadPriority.BelowNormal
};
backgroundThread.Start();

Avoiding Deadlocks

// Always acquire locks in consistent order
private static readonly object lock1 = new object();
private static readonly object lock2 = new object();

public void Method1() {
    lock (lock1) {
        lock (lock2) {
            // Work
        }
    }
}

public void Method2() {
    lock (lock1) { // Same order as Method1
        lock (lock2) {
            // Work
        }
    }
}

Single Instance Application Pattern

internal static class Program {
    [STAThread]
    private static void Main() {
        if (IsFirstInstance()) {
            Application.ApplicationExit += OnApplicationExit;
            Application.Run(new Form1());
        }
    }

    private static bool IsFirstInstance() {
        _mutex = new Mutex(false, Assembly.GetEntryAssembly().FullName);
        bool owned = _mutex.WaitOne(TimeSpan.Zero, false);
        return owned;
    }

    private static void OnApplicationExit(object sender, EventArgs e) {
        _mutex.ReleaseMutex();
        _mutex.Close();
    }

    private static Mutex _mutex;
}

Component-Based Programming

Interface-Based Design

// Define contracts through interfaces
public interface IDocumentProcessor {
    void ProcessDocument(IDocument document);
    bool CanProcess(string fileType);
}

public interface IDocument {
    string Title { get; }
    string Content { get; }
    DateTime Created { get; }
}

// Implement specific processors
public class PdfProcessor : IDocumentProcessor {
    public void ProcessDocument(IDocument document) {
        // PDF-specific processing
    }
    
    public bool CanProcess(string fileType) {
        return fileType.Equals(".pdf", StringComparison.OrdinalIgnoreCase);
    }
}

Component Factory Pattern

public class ProcessorFactory {
    private readonly List<IDocumentProcessor> _processors;
    
    public ProcessorFactory() {
        _processors = new List<IDocumentProcessor> {
            new PdfProcessor(),
            new WordProcessor(),
            new TextProcessor()
        };
    }
    
    public IDocumentProcessor GetProcessor(string fileType) {
        return _processors.FirstOrDefault(p => p.CanProcess(fileType));
    }
}

Disposable Pattern

public class ResourceManager : IDisposable {
    private bool _disposed = false;
    private FileStream _fileStream;
    
    public void DoWork() {
        CheckDisposed();
        // Use resources
    }
    
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing) {
        if (!_disposed) {
            if (disposing) {
                // Dispose managed resources
                _fileStream?.Dispose();
            }
            
            // Dispose unmanaged resources
            _disposed = true;
        }
    }
    
    private void CheckDisposed() {
        if (_disposed) {
            throw new ObjectDisposedException(GetType().Name);
        }
    }
}

Development Tools

ReSharper Productivity

ReSharper enhances C# development with:

  • Code generation: Properties, constructors, overrides
  • Refactoring: Rename, extract method, move to namespace
  • Navigation: Go to declaration, find usages
  • Code analysis: Suggestions and warnings
  • Live templates: Reusable code snippets

Visual Studio Tips

// Use regions for code organization
#region Private Fields
private string _name;
private int _age;
#endregion

// XML documentation
/// <summary>
/// Calculates the total price including tax
/// </summary>
/// <param name="basePrice">The base price before tax</param>
/// <param name="taxRate">The tax rate (e.g., 0.08 for 8%)</param>
/// <returns>The total price including tax</returns>
public decimal CalculateTotal(decimal basePrice, decimal taxRate) {
    return basePrice * (1 + taxRate);
}

Windows Live Writer for Documentation

Windows Live Writer provides a rich text editor for creating documentation and blog posts with:

  • WYSIWYG editing
  • Plugin support
  • Code syntax highlighting
  • Image handling

Design Patterns in .NET

Event Aggregator Pattern

public interface IEventAggregator {
    void Subscribe<T>(Action<T> handler);
    void Publish<T>(T eventObj);
}

public class EventAggregator : IEventAggregator {
    private readonly Dictionary<Type, List<Delegate>> _handlers 
        = new Dictionary<Type, List<Delegate>>();
    
    public void Subscribe<T>(Action<T> handler) {
        var eventType = typeof(T);
        if (!_handlers.ContainsKey(eventType)) {
            _handlers[eventType] = new List<Delegate>();
        }
        _handlers[eventType].Add(handler);
    }
    
    public void Publish<T>(T eventObj) {
        var eventType = typeof(T);
        if (_handlers.ContainsKey(eventType)) {
            foreach (Action<T> handler in _handlers[eventType]) {
                handler(eventObj);
            }
        }
    }
}

Identity Map Pattern

public class IdentityMap<TKey, TValue> {
    private readonly Dictionary<TKey, TValue> _map 
        = new Dictionary<TKey, TValue>();
    
    public void Add(TKey key, TValue value) {
        _map[key] = value;
    }
    
    public TValue Get(TKey key) {
        return _map.ContainsKey(key) ? _map[key] : default(TValue);
    }
    
    public bool Contains(TKey key) {
        return _map.ContainsKey(key);
    }
}

// Usage in data access layer
public class CustomerRepository {
    private readonly IdentityMap<int, Customer> _customerMap 
        = new IdentityMap<int, Customer>();
    
    public Customer GetById(int id) {
        if (_customerMap.Contains(id)) {
            return _customerMap.Get(id);
        }
        
        var customer = LoadFromDatabase(id);
        _customerMap.Add(id, customer);
        return customer;
    }
}

Adapter Pattern

// Legacy interface
public interface ILegacyPaymentProcessor {
    void ProcessPayment(string cardNumber, double amount);
}

// New interface
public interface IModernPaymentProcessor {
    PaymentResult ProcessPayment(PaymentInfo payment);
}

// Adapter
public class PaymentAdapter : IModernPaymentProcessor {
    private readonly ILegacyPaymentProcessor _legacyProcessor;
    
    public PaymentAdapter(ILegacyPaymentProcessor legacyProcessor) {
        _legacyProcessor = legacyProcessor;
    }
    
    public PaymentResult ProcessPayment(PaymentInfo payment) {
        try {
            _legacyProcessor.ProcessPayment(payment.CardNumber, payment.Amount);
            return new PaymentResult { Success = true };
        } catch (Exception ex) {
            return new PaymentResult { 
                Success = false, 
                ErrorMessage = ex.Message 
            };
        }
    }
}

Best Practices Summary

Exception Handling

  • Don’t swallow exceptions
  • Use specific exception types
  • Log exceptions with context
  • Fail fast when appropriate

Performance

  • Use StringBuilder for string concatenation
  • Prefer as over direct casting
  • Choose appropriate collection interfaces
  • Dispose resources properly

Code Quality

  • Follow SOLID principles
  • Write unit tests
  • Use meaningful names
  • Keep methods small and focused

Architecture

  • Separate concerns
  • Program to interfaces
  • Use dependency injection
  • Implement proper error handling

This guide represents foundational .NET and C# development practices from the framework’s early years, many of which remain relevant today. While newer versions of .NET have introduced additional features and improvements, these core concepts provide a solid foundation for understanding enterprise .NET development.


This guide consolidates knowledge from multiple blog posts written between 2006-2008, covering practical .NET development experience during the framework’s early adoption period.