Skip to main content

C# Best Practices and Interview Q&A

Level: Intermediate to Advanced
Goal: Write professional-quality C# code that others can read and maintain.

Good code is:

  • Readable: Easy to understand at a glance
  • Maintainable: Easy to change without breaking things
  • Performant: Runs efficiently
  • Testable: Can be tested easily

This article brings together lessons from previous 29 articles.

ℹ️ What You'll Learn
  • C# naming conventions - PascalCase, camelCase, _camelCase
  • Code quality rules - readonly, var, null checks, async/await
  • 20+ interview questions covering core C# concepts
  • SOLID principles in practice
  • Common mistakes and how to avoid them
  • Code review checklist for quality code
  • Performance tips and profiling
  • Real-world best practices from .NET industry

Best Practices Quick Reference

AreaPracticeWhyExample
NamingPascalCase for classes/methodsConvention, readabilityStudentService, GetGrade()
NamingcamelCase for locals/paramsC# standardstudentName, totalMarks
Naming_camelCase for private fieldsDistinguish from localsprivate int _count;
NamingI-prefix for interfacesIdentify contractsIStudentRepository, IGradable
CodeUse readonly when possibleImmutability, thread-safetyprivate readonly IRepo _repo;
Codevar when type obviousCleaner syntaxvar students = new List<Student>();
CodeArgumentNullException.ThrowIfNullGuard clause.ThrowIfNull(student)
Codeusing for disposablesAlways cleanupusing var conn = new SqlConnection();
AsyncNever .Result or .Wait()Deadlock riskAlways await
CollectionsReturn IEnumerable<T> from methodsFlexibilityIEnumerable<Student> GetAll()
Error HandlingCatch specific exceptionsClaritycatch (FileNotFoundException) not catch (Exception)
PerformanceUse StringBuilder in loopsAvoid GC pressureLoop through items, append to SB

Quick Definitions

  • Best practice - Standard approach for writing quality code
  • Naming convention - PascalCase, camelCase, CONSTANT_CASE rules
  • Code review - Team checks code before merge
  • SOLID - Five principles for maintainable code
  • DRY - Don't Repeat Yourself (reuse code)
  • YAGNI - You Aren't Gonna Need It (no over-engineering)
  • Immutable - Cannot change after creation
  • Return early - Guard clauses to exit early
  • Single responsibility - One class = one reason to change
  • Code smell - Pattern indicating deeper problem

When You'll Use This in SMS

SMS code should follow best practices:

// OK GOOD - readable names, clear intent
public async Task<StudentEnrollmentResult> EnrollStudentAsync(
Student student,
string className,
CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(student);

if (student.Age < 5 || student.Age > 25)
return StudentEnrollmentResult.InvalidAge;

// Process enrollment
return await _enrollmentService.ProcessAsync(student, className, ct);
}

// Wrong BAD - unclear names, mixed concerns, no error handling
public StudentEnrollmentResult E(Student s, string c)
{
var result = ProcessStudent(s, c, LogError, SendEmail, SaveDb);
return result; // What could go wrong?
}

Real impact: Without practices = unmaintainable code, slow onboarding, frequent bugs. With practices = clean, testable, sustainable codebase.


Try This Now

  1. Review existing SMS code for naming clarity
  2. Refactor one method: add guard clauses, single responsibility
  3. Extract 3 magic strings to named constants
  4. Add XML documentation to 5 public methods
  5. Check code follows SOLID principles

ℹ️ Video Tutorial

Best practices explained: naming conventions, SOLID principles, code smells, when to refactor, interview tips. Video coming soon. Subscribe to NexCoding YouTube for updates.


Naming Conventions

// Classes, Methods, Properties - PascalCase
public class StudentService { }
public void CalculateGrade() { }
public string FullName { get; }

// Local variables, parameters - camelCase
string studentName = "";
int totalMarks = 0;

// Private fields - _camelCase
private readonly IStudentRepository _repository;
private int _count;

// Constants - PascalCase or UPPER_SNAKE
const int MaxStudentsPerClass = 40;
const string DefaultSection = "A";

// Interfaces - I prefix
public interface IStudentRepository { }
public interface IGradable { }

// Async methods - Async suffix
public async Task<Student> GetStudentAsync(int id) { }

Code Quality Rules

// 1. Use readonly for fields set only in constructor
private readonly IStudentRepository _repo;
private readonly string _connectionString;

// 2. Prefer var when type is obvious
var students = new List<Student>(); // ?
var result = GetSomething(); // -- - unclear

// 3. Null checks - use modern syntax
ArgumentNullException.ThrowIfNull(student); // .NET 6+
ArgumentException.ThrowIfNullOrEmpty(name); // .NET 7+

// 4. async all the way - never block
// -- Deadlock risk
var data = GetDataAsync().Result;
// ?
var data = await GetDataAsync();

// 5. using for disposables - always
using var conn = new SqlConnection(cs);
using var writer = new StreamWriter(path);

// 6. StringBuilder in loops
var sb = new StringBuilder();
foreach (var s in students) sb.AppendLine(s.Name);

// 7. Return IEnumerable not List from methods
IEnumerable<Student> GetPassedStudents() { } // ? flexible
// Return List only if caller needs to add/remove

// 8. Early return - no deep nesting
// ? Deep nesting
if (student != null)
if (student.IsActive)
if (student.Percentage >= 35)
Console.WriteLine("Pass");

// ? Early return / guard clauses
if (student is null) return;
if (!student.IsActive) return;
if (student.Percentage < 35) return;
Console.WriteLine("Pass");

Common Mistakes

? Using .Result or .Wait() on async methods (deadlock risk):

var data = GetDataAsync().Result; // Blocks thread, can deadlock

? Always use await:

var data = await GetDataAsync(); // Thread free, no deadlock

? Creating new object in loop (GC pressure):

var results = new List<Student>();
foreach (var id in studentIds)
{
var student = new Student(); // Created 1000 times
results.Add(student);
}

? Create once, reuse:

var student = new Student();
foreach (var id in studentIds)
{
student.Id = id;
results.Add(student);
}

? Not checking null before using:

var student = GetStudent(999);
Console.WriteLine(student.Name); // NullReferenceException if not found

? Guard clause:

var student = GetStudent(999);
if (student is null) return;
Console.WriteLine(student.Name); // Safe

Best Practices Summary

  1. Follow naming conventions - Consistency aids readability
  2. Use readonly for immutable fields - Prevent accidental changes
  3. Prefer var for obvious types - Cleaner code
  4. Always validate inputs - Fail fast with clear messages
  5. Use async/await consistently - Never .Result or .Wait()
  6. Return interfaces, not concrete types - Flexibility for callers
  7. Write small methods - One responsibility, easy to test
  8. Use early return - Avoid deep nesting
  9. Use StringBuilder in loops - Avoid GC pressure
  10. Test all paths - Unit tests catch edge cases
  11. Document complex logic - Comments explain "why", not "what"
  12. Profile before optimizing - Measure, don't guess

Top 20+ C# Interview Questions

🎯 Q1: Value type vs Reference type?

Value type: Stored on stack. Copied on assignment. int, double, bool, struct.

Reference type: Stored on heap. Reference copied on assignment. class, string, array.

int x = 10;
int y = x; // y = 10, copy of value
y = 20; // x still 10

var s1 = new Student { Name = "Sahasra" };
var s2 = s1; // s2 = reference to same object
s2.Name = "Priya"; // s1.Name also "Priya"!

Value types safe for math. Reference types for objects. Know the difference.

🎯 Q2: What is boxing/unboxing?

Boxing = value type ? object (heap allocation). Unboxing = object ? value type.

int value = 42;
object boxed = value; // Boxing - allocation on heap
int unboxed = (int)boxed; // Unboxing - copy from heap

Avoid in loops - causes GC pressure, allocations slow down code.

🎯 Q3: Abstract class vs Interface?

Abstract class: IS-A relationship. Shared code, single inheritance.

Interface: CAN-DO capability. Multiple implementation, no code (pre-C# 8).

Abstract for related types with shared code. Interfaces for capabilities.

🎯 Q4: IEnumerable vs IQueryable?

IEnumerable: In-memory. LINQ runs in C#. All data loaded first.

IQueryable: Translates LINQ to SQL (EF Core). Filtered at database.

For DB: use IQueryable, let database filter. For in-memory: IEnumerable OK.

🎯 Q5: Explain async/await?

async = marks method as asynchronous. await = suspends until Task completes. Thread freed for other work.

Key: Thread doesn't block during I/O. Can handle 1000s concurrent requests.

🎯 Q6: Delegate vs Event?

Delegate: Type-safe method reference. Caller can invoke directly.

Event: Delegate + protection. Outside code can only +=, -=, not invoke.

Events safer - encapsulation. Publisher controls when notifications sent.

🎯 Q7: What is LINQ?

Language Integrated Query - query collections in C# syntax.

Works on: List, Array, IQueryable (DB), XML.

Deferred execution - query runs when enumerated (.ToList(), foreach).

Powerful for data processing, less code than loops.

🎯 Q8: throw vs throw ex?

throw; = preserves stack trace. throw ex; = resets to catch block.

Always use throw; when re-throwing.

🎯 Q9: Generics - why use them?

Type safety without duplication. List<Student> instead of ArrayList.

Repository<T> works for any entity type. Write once, use everywhere.

🎯 Q10: Sealed class?

Cannot be inherited. Used when no further extension allowed.

string is sealed. records sealed by default.

Useful when: design complete, no subclassing needed, performance critical.

🎯 Q11: const vs readonly?

const = compile-time, must initialize at declaration. readonly = runtime, set in constructor.

Use const for never-changing values. Use readonly for values known at runtime.

🎯 Q12: IDisposable - when implement?

When class holds unmanaged resources: file handles, DB connections, HTTP clients.

Pattern: Dispose(bool), GC.SuppressFinalize, ~Finalizer.

Always use with using statement. See Article 24 for full Dispose pattern.

🎯 Q13: Difference between == and .Equals()?

String: == compares value. Class: == compares reference, .Equals() compares value. Record: == compares value (built-in).

Override .Equals() for custom classes if value equality needed.

🎯 Q14: What is a record?

Immutable reference type with value equality built-in.

Best for DTOs, API responses, value objects.

Records = concise, immutable, value-equal. Perfect for data.

🎯 Q15: Lazy<T> - what is it?

Lazy initialization. Instance created only when first accessed. Thread-safe by default.

Used for: Singleton pattern (thread-safe), expensive initialization.

🎯 Q16: What is yield return?

Lazy iterator - generates values one at a time.

IEnumerable method with yield return pauses at each return.

See Article 22 for full yield explanation.

🎯 Q17: Nullable reference types?

C# 8+ feature. string? = nullable, string = non-nullable (warning if null).

Catches NullReferenceException at compile time.

See Article 23 for nullable types deep dive.

🎯 Q18: Extension methods - restrictions?

Must be: static class, static method, first param = this T.

Cannot: access private members, override existing methods.

Can: extend sealed classes, built-in types (string, int, IEnumerable).

See Article 21 for extension methods.

🎯 Q19: Explain Dependency Injection?

Objects receive dependencies from outside rather than creating them.

Makes code testable (inject mock in tests, real in production).

ASP.NET Core has built-in DI container (IServiceCollection).

DI = flexibility, testability, loose coupling.

🎯 Q20: Performance tips?
  1. Use async/await for I/O - never .Result or .Wait()
  2. StringBuilder for string concatenation in loops
  3. Span<T> for zero-allocation string/array slicing
  4. Avoid LINQ in hot paths - use for loops
  5. Pool objects with ArrayPool<T> / ObjectPool<T>
  6. Profile before optimizing - measure, don't guess

Profiling > guessing. Measure first.


🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on C# best practices and interview preparation. Try these prompts:

  • "Quiz me on top C# interview questions - ask 5 questions one by one and wait for my answers"
  • "What are the most common C# mistakes junior developers make in job interviews?"
  • "What C# topics should I focus on for a .NET backend developer job interview?"
  • "Review this C# code and tell me what best practices I am violating"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

?? C# Complete!

Congratulations - you have covered all 30 C# articles. You are ready to start ASP.NET Core.

Next Section: .NET Introduction ->

Next Article

Reserved Keywords ->

nexcoding.in