Every object in your application is born somewhere. Most of the time it's a simple new:

var service = new OrderService(); 

And most of the time that's perfectly fine. But when the class you create has dependencies, when you need to decide at runtime which class to create, or when creating it is expensive, that innocent new becomes a problem: it ties your code to a concrete type and spreads creation logic everywhere.

This is exactly the kind of problem creational patterns solve. They help you with three things:

  • Controlling the complexity of object creation;
  • Managing object lifecycles;
  • Providing alternatives to direct instantiation when it becomes a problem.

The family at a glance

Pattern What it does Use it when
Singleton (GoF) Ensures a class has only one instance, with global access One shared instance for the whole app (in .NET, usually a DI lifetime)
Factory Method (GoF) Lets subclasses decide which class to create The creation step varies by subclass
Simple Factory Puts the creation decision in one class or method The choice depends on runtime data
Abstract Factory (GoF) Creates families of related objects Objects must always match each other
Builder (GoF) Builds complex objects step by step Many optional parameters, or a fluent API
Prototype (GoF) Creates new objects by cloning existing ones Copying is cheaper or simpler than building
Object Pool Reuses expensive objects instead of recreating them Creation is costly and the objects are short-lived
Lazy Initialization Defers creation until the object is needed The object is expensive and may never be used
Dependency Injection Gives objects their dependencies from outside Almost always, in modern .NET
Service Locator A central registry to look services up Rarely: it hides dependencies, so it's often an anti-pattern

What modern .NET changes

The intent of these patterns hasn't changed since 1994. What changed is how much code you need to write for them.

The DI container does a lot of the work. The classic Singleton, with its locks and thread-safety concerns, is often just a lifetime today:

builder.Services.AddSingleton<IPriceCalculator, PriceCalculator>(); 

The container creates the instance once, shares it, and disposes it at shutdown. Scoped, transient and singleton lifetimes are first-class concepts, handled the same way across the whole application.

The language helps too. Records and init-only properties give you immutable objects without the boilerplate, and the with expression is a built-in, shallow Prototype:

public record Customer(string FirstName, string LastName, int Age); var customer = new Customer("Mario", "Rossi", 40); var olderCustomer = customer with { Age = 41 }; // a copy, the original is untouched 

Switch expressions make simple factories short and explicit:

public static INotification Create(NotificationChannel channel) => channel switch { NotificationChannel.Email => new EmailNotification(), NotificationChannel.Sms => new SmsNotification(), _ => throw new NotSupportedException($"No notification for {channel}") }; 

Performance is part of the picture. The garbage collector is very good at short-lived objects, so pooling is not a default anymore. But for large buffers and expensive resources, ArrayPool<T>, ObjectPool<T> and connection pools are still the right tool.

A few rules that hold for all of them

  • Start with new and the DI container. Reach for a pattern when a real problem shows up, not before.
  • Keep creation in one place. Whether it's a factory, a builder or the composition root, scattered new calls are the real smell.
  • Prefer immutable objects. They make many creational problems, thread safety first of all, disappear.
  • Don't let the container leak. Injecting IServiceProvider everywhere turns Dependency Injection back into a Service Locator.

Want the full story?

This is the short version. The complete article on Level Up Coding goes through how object creation evolved with the language and the platform, from classic OOP to records, pattern matching and the built-in DI container. It also covers performance and the garbage collector, immutability and thread safety, cloud-native constraints, generics and static abstract members, testing, and the trends that are shaping the next years.

Read it here: Creational Patterns in .NET C#: Building objects in the modern Era.

Each pattern then has its own deep dive, starting with the Singleton.

Thanks for reading.