Object-Oriented Programming (OOP) organises code around objects — self-contained units that combine data (fields and properties) and behaviour (methods). In C#, you define the structure of an object using a class, which acts as a blueprint. You then create individual instances (objects) from that blueprint using the new keyword. Every entity model, DTO, service, and controller in an ASP.NET Core application is a class. Understanding classes thoroughly is the most important foundation in this series.
Defining a Class
// A class groups related data and behaviour together
public class BlogPost
{
// ── Fields — raw data storage (usually private) ────────────────────────
private string _title = string.Empty;
private int _viewCount;
// ── Properties — controlled access to data ─────────────────────────────
public int Id { get; set; }
public string Title
{
get => _title;
set => _title = value?.Trim() ?? string.Empty; // validation in setter
}
public string Body { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
public bool IsPublished { get; set; }
public int ViewCount { get => _viewCount; private set => _viewCount = value; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// ── Methods — behaviour ────────────────────────────────────────────────
public void Publish()
{
if (string.IsNullOrWhiteSpace(Title))
throw new InvalidOperationException("Cannot publish a post without a title.");
IsPublished = true;
}
public void IncrementView() => _viewCount++;
public override string ToString() => $"[{Id}] {Title} ({(IsPublished ? "Published" : "Draft")})";
}
// Creating instances
var post1 = new BlogPost(); // default constructor
post1.Id = 1;
post1.Title = " Hello, World! "; // setter trims whitespace
post1.Body = "My first blog post.";
Console.WriteLine(post1.Title); // "Hello, World!" (trimmed by setter)
Console.WriteLine(post1.ToString()); // "[1] Hello, World! (Draft)"
post1.Publish();
Console.WriteLine(post1.IsPublished); // true
Note: The naming convention in C# is PascalCase for class names (
BlogPost, not blogPost or blog_post), PascalCase for public members (Title, Publish()), and camelCase with a leading underscore for private fields (_title, _viewCount). These conventions are enforced by code analysis tools like Roslyn Analyzers and are expected in any professional .NET codebase. Visual Studio’s default code style settings enforce them automatically.Tip: Every class should be in its own file, and the filename should match the class name exactly:
BlogPost.cs for the BlogPost class. This is not enforced by the compiler, but it is a universal .NET convention — IDEs rely on it for navigation, and code reviewers expect it. Grouping multiple classes in one file is occasionally acceptable for small private helper classes, but public classes should always have their own file.Warning: Avoid public fields — use properties instead. Public fields (
public string Title;) expose the storage location directly, preventing you from ever adding validation, logging, or computed logic without breaking all callers. Properties (public string Title { get; set; }) look identical at the call site but give you full control over the get/set behaviour. Entity Framework Core and ASP.NET Core model binding require properties, not fields, to work correctly.Class vs Object
// BlogPost is the CLASS — the blueprint/template (one definition)
// post1, post2 are OBJECTS — instances created from the blueprint (many instances)
var post1 = new BlogPost { Id = 1, Title = "First Post", Body = "..." };
var post2 = new BlogPost { Id = 2, Title = "Second Post", Body = "..." };
// Each instance has its own independent data
post1.IncrementView();
post1.IncrementView();
Console.WriteLine(post1.ViewCount); // 2
Console.WriteLine(post2.ViewCount); // 0 — independent from post1
// Reference equality — two variables can point to the same object
BlogPost alias = post1; // alias references the SAME object as post1
alias.IncrementView();
Console.WriteLine(post1.ViewCount); // 3 — alias and post1 are the same object
Common Mistakes
Mistake 1 — Using a public field instead of a property
❌ Wrong — exposes raw storage, breaks EF Core and model binding:
public class Post { public string Title; } // field — avoid!
✅ Correct — always use properties for public data:
public class Post { public string Title { get; set; } = string.Empty; }
Mistake 2 — File name does not match class name
❌ Wrong: Models.cs containing public class BlogPost
✅ Correct: BlogPost.cs containing public class BlogPost