Skip to main content

Essential .NET Libraries and Types: Your Development Toolkit

The .NET platform comes with an extensive Base Class Library (BCL) that provides thousands of ready-to-use types and APIs. Understanding these essential libraries will make you a more productive .NET developer. This guide covers the most important libraries and types you'll use in everyday development.

Base Class Library (BCL) Overview

The Base Class Library is the foundation of all .NET applications. It provides:

  • Fundamental Types: Basic data types and operations
  • Collections: Data structures for organizing objects
  • I/O Operations: File, network, and stream handling
  • Threading: Parallel and asynchronous programming
  • Reflection: Runtime type inspection and manipulation
  • Globalization: International application support

BCL Architecture

┌─────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────┤
│ Application Frameworks │ ← ASP.NET Core, WPF, etc.
├─────────────────────────────────────────┤
│ Base Class Library (BCL) │ ← Core types and APIs
├─────────────────────────────────────────┤
│ .NET Runtime │ ← CLR execution engine
└─────────────────────────────────────────┘

Fundamental Types

Primitive Types

.NET provides a comprehensive set of primitive types:

Primitive Types
// Integral types
byte smallNumber = 255; // 0 to 255
sbyte signedSmall = -128; // -128 to 127
short shortNumber = 32767; // -32,768 to 32,767
ushort unsignedShort = 65535; // 0 to 65,535
int number = 2147483647; // -2^31 to 2^31-1
uint unsignedInt = 4294967295; // 0 to 2^32-1
long bigNumber = 9223372036854775807; // -2^63 to 2^63-1
ulong unsignedBig = 18446744073709551615; // 0 to 2^64-1

// Floating-point types
float singlePrecision = 3.14f; // 32-bit floating point
double doublePrecision = 3.14159; // 64-bit floating point
decimal preciseDecimal = 19.99m; // 128-bit precise decimal

// Other types
bool isValid = true;
char character = 'A';
string text = "Hello, World!";

Nullable Types

Handle the absence of values safely:

Nullable Types
// Value types can be nullable
int? nullableInt = null;
DateTime? optionalDate = null;

// Check for null before using
if (nullableInt.HasValue)
{
Console.WriteLine($"Value: {nullableInt.Value}");
}

// Null-coalescing operators
int result = nullableInt ?? 0; // Use 0 if null
nullableInt ??= 42; // Assign 42 if null

// Nullable reference types (C# 8+)
#nullable enable
string? optionalText = null; // Can be null
string requiredText = "Hello"; // Cannot be null

DateTime and DateTimeOffset

Work with dates and times effectively:

Date and Time Operations
// Current date and time
DateTime now = DateTime.Now; // Local time
DateTime utcNow = DateTime.UtcNow; // UTC time
DateTimeOffset offset = DateTimeOffset.Now; // Time with timezone

// Creating specific dates
var specificDate = new DateTime(2024, 12, 25, 10, 30, 0);
var dateOnly = new DateOnly(2024, 12, 25); // C# 10+
var timeOnly = new TimeOnly(14, 30, 0); // C# 10+

// Date operations
DateTime tomorrow = DateTime.Today.AddDays(1);
DateTime nextWeek = DateTime.Today.AddDays(7);
TimeSpan difference = nextWeek - DateTime.Today;

// Formatting dates
string formatted = now.ToString("yyyy-MM-dd HH:mm:ss");
string custom = now.ToString("MMMM dd, yyyy");

// Parsing dates
if (DateTime.TryParse("2024-12-25", out DateTime parsed))
{
Console.WriteLine($"Parsed: {parsed}");
}

Collections Framework

.NET provides a rich set of collection types for organizing data:

Arrays

The most basic collection type:

Array Operations
// Array declaration and initialization
int[] numbers = new int[5]; // Fixed size
int[] primes = { 2, 3, 5, 7, 11 }; // With values
string[] names = new string[] { "Alice", "Bob", "Charlie" };

// Multi-dimensional arrays
int[,] matrix = new int[3, 3];
int[,] grid = { {1, 2}, {3, 4} };

// Jagged arrays
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2] { 1, 2 };
jaggedArray[1] = new int[4] { 3, 4, 5, 6 };

// Array operations
Array.Sort(numbers); // Sort in place
Array.Reverse(names); // Reverse in place
int index = Array.IndexOf(primes, 5); // Find index
bool contains = Array.Exists(primes, p => p > 10);

List<T>

The most commonly used dynamic collection:

List<T> Operations
// List creation
var numbers = new List<int>();
var names = new List<string> { "Alice", "Bob", "Charlie" };
var products = new List<Product>(100); // With initial capacity

// Adding elements
numbers.Add(42);
numbers.AddRange(new[] { 1, 2, 3 });
numbers.Insert(0, 100); // Insert at index

// Accessing elements
int first = numbers[0];
int last = numbers[numbers.Count - 1];

// Removing elements
numbers.Remove(42); // Remove first occurrence
numbers.RemoveAt(0); // Remove at index
numbers.RemoveAll(n => n < 10); // Remove all matching

// Searching
bool contains = numbers.Contains(42);
int index = numbers.IndexOf(42);
Product? product = products.Find(p => p.Name == "Widget");
List<Product> expensive = products.FindAll(p => p.Price > 100);

// Sorting
numbers.Sort();
products.Sort((p1, p2) => p1.Price.CompareTo(p2.Price));

Dictionary<TKey, TValue>

Key-value pairs for fast lookups:

Dictionary Operations
// Dictionary creation
var scores = new Dictionary<string, int>();
var config = new Dictionary<string, string>
{
["ConnectionString"] = "Server=localhost;Database=MyDb",
["ApiKey"] = "abc123",
["MaxRetries"] = "3"
};

// Adding and updating
scores["Alice"] = 95;
scores["Bob"] = 87;
scores.Add("Charlie", 92);

// Safe operations
if (scores.TryGetValue("Alice", out int aliceScore))
{
Console.WriteLine($"Alice's score: {aliceScore}");
}

scores.TryAdd("David", 88); // Only adds if key doesn't exist

// Iteration
foreach (var kvp in scores)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}

foreach (string name in scores.Keys)
{
Console.WriteLine($"Player: {name}");
}

HashSet<T>

Unique elements with fast lookups:

HashSet Operations
var uniqueNumbers = new HashSet<int> { 1, 2, 3, 4, 5 };
var visitedPages = new HashSet<string>();

// Adding elements (duplicates ignored)
uniqueNumbers.Add(6); // Added
uniqueNumbers.Add(3); // Ignored (already exists)

// Set operations
var set1 = new HashSet<int> { 1, 2, 3, 4 };
var set2 = new HashSet<int> { 3, 4, 5, 6 };

set1.UnionWith(set2); // set1 now contains {1, 2, 3, 4, 5, 6}
set1.IntersectWith(set2); // set1 now contains {3, 4, 5, 6}
set1.ExceptWith(set2); // set1 now contains {1, 2}

bool isSubset = set1.IsSubsetOf(set2);
bool overlaps = set1.Overlaps(set2);

Queue<T> and Stack<T>

FIFO and LIFO collections:

Queue and Stack
// Queue (First In, First Out)
var queue = new Queue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
queue.Enqueue("Third");

string first = queue.Dequeue(); // "First"
string peek = queue.Peek(); // "Second" (doesn't remove)

// Stack (Last In, First Out)
var stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
stack.Push(3);

int top = stack.Pop(); // 3
int peek2 = stack.Peek(); // 2 (doesn't remove)

String Manipulation

Strings are fundamental in most applications:

String Operations

String Operations
string text = "  Hello, World!  ";

// Basic operations
int length = text.Length;
char first = text[0];
bool isEmpty = string.IsNullOrEmpty(text);
bool isWhitespace = string.IsNullOrWhiteSpace(text);

// Modification (strings are immutable - these return new strings)
string upper = text.ToUpper();
string lower = text.ToLower();
string trimmed = text.Trim();
string replaced = text.Replace("World", "Universe");
string substring = text.Substring(2, 5);

// Splitting and joining
string csv = "apple,banana,cherry";
string[] fruits = csv.Split(',');
string joined = string.Join(" | ", fruits);

// String comparison
bool equal = string.Equals(text, "hello", StringComparison.OrdinalIgnoreCase);
int comparison = string.Compare("apple", "banana", StringComparison.Ordinal);

StringBuilder for Efficient String Building

When building strings in loops, use StringBuilder:

StringBuilder Usage
// ❌ Inefficient - creates many temporary strings
string BuildReport(List<Customer> customers)
{
string report = "";
foreach (var customer in customers)
{
report += $"{customer.Name}: {customer.Orders.Count} orders\n";
}
return report;
}

// ✅ Efficient - modifies internal buffer
string BuildReportEfficient(List<Customer> customers)
{
var sb = new StringBuilder(customers.Count * 50); // Pre-size if possible

foreach (var customer in customers)
{
sb.AppendLine($"{customer.Name}: {customer.Orders.Count} orders");
}

return sb.ToString();
}

// StringBuilder methods
var builder = new StringBuilder();
builder.Append("Hello");
builder.Append(' ');
builder.Append("World");
builder.AppendLine("!");
builder.Insert(0, ">> ");
builder.Replace("World", "Universe");
string result = builder.ToString();

String Formatting and Interpolation

String Formatting
string name = "Alice";
int age = 25;
decimal salary = 75000.50m;

// String interpolation (C# 6+)
string message = $"Hello, {name}! You are {age} years old.";
string formatted = $"Salary: {salary:C2}"; // Currency format

// Composite formatting
string composite = string.Format("Hello, {0}! You are {1} years old.", name, age);

// Format specifiers
string currency = $"{salary:C}"; // Currency: $75,000.50
string percent = $"{0.75:P}"; // Percentage: 75.00%
string date = $"{DateTime.Now:yyyy-MM-dd}"; // Date: 2024-01-15
string number = $"{1234567:N0}"; // Number: 1,234,567

File and I/O Operations

.NET provides comprehensive APIs for file and I/O operations:

File Operations

File Operations
string filePath = @"C:\temp\data.txt";
string content = "Hello, World!";

// Writing files
await File.WriteAllTextAsync(filePath, content);
await File.WriteAllLinesAsync(filePath, new[] { "Line 1", "Line 2" });
await File.WriteAllBytesAsync(filePath, Encoding.UTF8.GetBytes(content));

// Reading files
string fileContent = await File.ReadAllTextAsync(filePath);
string[] lines = await File.ReadAllLinesAsync(filePath);
byte[] bytes = await File.ReadAllBytesAsync(filePath);

// File information
bool exists = File.Exists(filePath);
DateTime lastWrite = File.GetLastWriteTime(filePath);
long size = new FileInfo(filePath).Length;

// File operations
File.Copy(filePath, @"C:\temp\backup.txt");
File.Move(filePath, @"C:\temp\renamed.txt");
File.Delete(filePath);

Directory Operations

Directory Operations
string dirPath = @"C:\temp\mydir";

// Directory operations
Directory.CreateDirectory(dirPath);
bool dirExists = Directory.Exists(dirPath);

// Listing contents
string[] files = Directory.GetFiles(dirPath);
string[] directories = Directory.GetDirectories(dirPath);
string[] allEntries = Directory.GetFileSystemEntries(dirPath);

// Recursive search
string[] txtFiles = Directory.GetFiles(dirPath, "*.txt", SearchOption.AllDirectories);

// Directory info
var dirInfo = new DirectoryInfo(dirPath);
FileInfo[] fileInfos = dirInfo.GetFiles("*.cs");
DirectoryInfo[] subDirs = dirInfo.GetDirectories();

Stream Operations

For more control over I/O operations:

Stream Operations
// File streams
using var fileStream = new FileStream(filePath, FileMode.Create);
using var writer = new StreamWriter(fileStream);
await writer.WriteLineAsync("Hello from stream!");

using var readStream = new FileStream(filePath, FileMode.Open);
using var reader = new StreamReader(readStream);
string line = await reader.ReadLineAsync();

// Memory streams
using var memoryStream = new MemoryStream();
using var streamWriter = new StreamWriter(memoryStream);
await streamWriter.WriteLineAsync("In-memory data");
await streamWriter.FlushAsync();

byte[] data = memoryStream.ToArray();
string result = Encoding.UTF8.GetString(data);

Path Operations

Safe path manipulation:

Path Operations
// Path construction
string fullPath = Path.Combine("C:", "Users", "Alice", "Documents", "file.txt");
string tempFile = Path.GetTempFileName();
string tempDir = Path.GetTempPath();

// Path information
string directory = Path.GetDirectoryName(fullPath); // C:\Users\Alice\Documents
string fileName = Path.GetFileName(fullPath); // file.txt
string nameOnly = Path.GetFileNameWithoutExtension(fullPath); // file
string extension = Path.GetExtension(fullPath); // .txt

// Path validation
bool isRooted = Path.IsPathRooted(fullPath); // true
char[] invalidChars = Path.GetInvalidFileNameChars();

Networking Fundamentals

.NET provides excellent networking support:

HttpClient for Web Requests

HttpClient Usage
public class ApiService
{
private readonly HttpClient _httpClient;

public ApiService(HttpClient httpClient)
{
_httpClient = httpClient;
}

// GET request
public async Task<User?> GetUserAsync(int userId)
{
try
{
var response = await _httpClient.GetAsync($"/api/users/{userId}");
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<User>(json);
}
catch (HttpRequestException ex)
{
// Handle HTTP errors
throw new ServiceException($"Failed to get user {userId}", ex);
}
}

// POST request
public async Task<User> CreateUserAsync(CreateUserRequest request)
{
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync("/api/users", content);
response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<User>(responseJson)!;
}

// PUT request with custom headers
public async Task UpdateUserAsync(int userId, UpdateUserRequest request)
{
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var requestMessage = new HttpRequestMessage(HttpMethod.Put, $"/api/users/{userId}")
{
Content = content
};
requestMessage.Headers.Add("X-Custom-Header", "value");

var response = await _httpClient.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
}
}

JSON Serialization

Working with JSON data:

JSON Operations
public class JsonService
{
private readonly JsonSerializerOptions _options;

public JsonService()
{
_options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}

// Serialize to JSON
public string SerializeObject<T>(T obj)
{
return JsonSerializer.Serialize(obj, _options);
}

// Deserialize from JSON
public T? DeserializeObject<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, _options);
}

// Work with JSON elements
public void ProcessDynamicJson(string json)
{
using var document = JsonDocument.Parse(json);
var root = document.RootElement;

if (root.TryGetProperty("name", out var nameElement))
{
string name = nameElement.GetString() ?? "";
Console.WriteLine($"Name: {name}");
}

if (root.TryGetProperty("items", out var itemsElement) &&
itemsElement.ValueKind == JsonValueKind.Array)
{
foreach (var item in itemsElement.EnumerateArray())
{
if (item.TryGetProperty("id", out var idElement))
{
int id = idElement.GetInt32();
Console.WriteLine($"Item ID: {id}");
}
}
}
}
}

Reflection and Metadata

Examine and manipulate types at runtime:

Reflection Examples
public class ReflectionService
{
public void ExamineType<T>()
{
Type type = typeof(T);

Console.WriteLine($"Type: {type.Name}");
Console.WriteLine($"Full Name: {type.FullName}");
Console.WriteLine($"Assembly: {type.Assembly.GetName().Name}");
Console.WriteLine($"Is Class: {type.IsClass}");
Console.WriteLine($"Is Interface: {type.IsInterface}");

// Properties
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"Property: {prop.Name} ({prop.PropertyType.Name})");
}

// Methods
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
foreach (var method in methods.Where(m => !m.IsSpecialName))
{
Console.WriteLine($"Method: {method.Name}");
}
}

public T CreateInstance<T>(params object[] args) where T : class
{
Type type = typeof(T);
return (T)Activator.CreateInstance(type, args)!;
}

public object? GetPropertyValue(object obj, string propertyName)
{
Type type = obj.GetType();
PropertyInfo? property = type.GetProperty(propertyName);
return property?.GetValue(obj);
}

public void SetPropertyValue(object obj, string propertyName, object value)
{
Type type = obj.GetType();
PropertyInfo? property = type.GetProperty(propertyName);
property?.SetValue(obj, value);
}
}

Threading and Concurrency

Handle parallel operations safely:

Task-based Programming

Task Operations
public class TaskService
{
// CPU-bound work
public async Task<int> CalculateAsync(int input)
{
return await Task.Run(() =>
{
// Simulate heavy computation
Thread.Sleep(1000);
return input * input;
});
}

// Running multiple tasks concurrently
public async Task<(int result1, int result2, int result3)> CalculateMultipleAsync()
{
var task1 = CalculateAsync(10);
var task2 = CalculateAsync(20);
var task3 = CalculateAsync(30);

// Wait for all to complete
await Task.WhenAll(task1, task2, task3);

return (await task1, await task2, await task3);
}

// Handling timeouts
public async Task<string> GetDataWithTimeoutAsync()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

try
{
return await GetDataAsync(cts.Token);
}
catch (OperationCanceledException)
{
return "Operation timed out";
}
}

private async Task<string> GetDataAsync(CancellationToken cancellationToken)
{
// Simulate long-running operation
await Task.Delay(10000, cancellationToken);
return "Data retrieved";
}
}

Thread-Safe Collections

Concurrent Collections
// Thread-safe collections
var concurrentDict = new ConcurrentDictionary<string, int>();
var concurrentQueue = new ConcurrentQueue<string>();
var concurrentBag = new ConcurrentBag<int>();

// Producer-consumer scenario
public class ProducerConsumer
{
private readonly ConcurrentQueue<WorkItem> _queue = new();
private readonly CancellationTokenSource _cts = new();

public void StartProcessing()
{
// Start producer
Task.Run(async () => await ProduceAsync(_cts.Token));

// Start multiple consumers
var consumers = Enumerable.Range(0, 3)
.Select(_ => Task.Run(async () => await ConsumeAsync(_cts.Token)))
.ToArray();
}

private async Task ProduceAsync(CancellationToken cancellationToken)
{
int counter = 0;
while (!cancellationToken.IsCancellationRequested)
{
var item = new WorkItem { Id = counter++, Data = $"Item {counter}" };
_queue.Enqueue(item);

await Task.Delay(100, cancellationToken);
}
}

private async Task ConsumeAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
if (_queue.TryDequeue(out var item))
{
await ProcessItemAsync(item);
}
else
{
await Task.Delay(50, cancellationToken);
}
}
}

private async Task ProcessItemAsync(WorkItem item)
{
// Process the work item
await Task.Delay(200);
Console.WriteLine($"Processed: {item.Data}");
}
}

Performance Best Practices

Memory Management

Memory-Efficient Code
public class PerformanceExamples
{
// ❌ Poor: Creates many temporary objects
public List<string> ProcessItemsPoor(IEnumerable<Customer> customers)
{
var result = new List<string>();
foreach (var customer in customers)
{
var processed = customer.Name.ToUpper().Trim() + " - " + customer.Email.ToLower();
result.Add(processed);
}
return result;
}

// ✅ Better: Minimize allocations
public List<string> ProcessItemsBetter(ICollection<Customer> customers)
{
var result = new List<string>(customers.Count); // Pre-size
var sb = new StringBuilder(256); // Reuse StringBuilder

foreach (var customer in customers)
{
sb.Clear();
sb.Append(customer.Name.ToUpperInvariant().AsSpan().Trim());
sb.Append(" - ");
sb.Append(customer.Email.ToLowerInvariant());
result.Add(sb.ToString());
}
return result;
}

// Using object pooling for expensive objects
private readonly ObjectPool<StringBuilder> _stringBuilderPool;

public string FormatMessage(string template, params object[] args)
{
var sb = _stringBuilderPool.Get();
try
{
sb.Clear();
sb.AppendFormat(template, args);
return sb.ToString();
}
finally
{
_stringBuilderPool.Return(sb);
}
}
}

Summary

The .NET Base Class Library provides a comprehensive foundation for building applications:

Key Library Categories

  1. Fundamental Types: Basic data types and operations
  2. Collections: Rich set of data structures
  3. String Manipulation: Powerful text processing capabilities
  4. File I/O: Comprehensive file and directory operations
  5. Networking: Modern HTTP and networking APIs
  6. JSON: Built-in serialization support
  7. Reflection: Runtime type inspection
  8. Threading: Async/await and parallel programming

Best Practices

  • Choose the Right Collection: Use appropriate collection types for your scenarios
  • Be Memory Conscious: Minimize allocations, especially in hot paths
  • Use Async/Await: For I/O-bound operations
  • Leverage LINQ: For data querying and transformation
  • Handle Exceptions: Proper error handling for robust applications
  • Follow Patterns: Use established patterns like using statements for resource management

Next Steps

Now that you understand the essential .NET libraries, it's time to learn the command-line tools:

Continue to Tutorial 5: Basic .NET CLI Commands to master the development workflow and tools.


Building a .NET application? PBX Digital specializes in modern .NET development. Contact us at mp@pbxdigital.net for expert consulting and development services.