Flyweight Pattern in C# to Reduce Memory
The Flyweight design pattern is a structural pattern designed to drastically minimize memory footprint by sharing common parts of state across thousands of similar objects instead of duplicating data in every instance. This guide covers the distinction between intrinsic and extrinsic state, demonstrates how to implement a flyweight cache using a factory, and provides a clear, runnable C# example showcasing massive memory savings when rendering repetitive objects.
Understanding Intrinsic vs. Extrinsic State
The core principle of the Flyweight pattern involves dividing an object's state into two categories:
- Intrinsic State: Constant, immutable data that is identical across many instances. This data lives inside the flyweight object and is shared.
- Extrinsic State: Unique, contextual data that changes with each instance. This data is stored outside the flyweight (often in client code) and passed to the flyweight methods when an operation is executed.
C# Implementation Example
Consider an application rendering a forest containing millions of trees. Storing 3D mesh data, textures, and color information individually for each tree exhausts memory rapidly. Instead, the Flyweight pattern stores the heavy visual data once and shares it across all tree locations.
1. Define the Flyweight (Intrinsic State)
The TreeType class contains the shared intrinsic data
(name, color, and complex texture data) and a method that accepts the
extrinsic data (coordinates) to perform an action.
public class TreeType
{
public string Name { get; }
public string Color { get; }
public byte[] TextureData { get; } // Heavy memory consumer
public TreeType(string name, string color, byte[] textureData)
{
Name = name;
Color = color;
TextureData = textureData;
}
public void Draw(int x, int y)
{
Console.WriteLine($"Drawing '{Name}' ({Color}) at ({x}, {y})");
}
}2. Create the Flyweight Factory
The TreeFactory maintains a pool of existing flyweights
and returns an existing instance or creates a new one if it does not yet
exist.
using System.Collections.Generic;
public class TreeFactory
{
private static readonly Dictionary<string, TreeType> _treeTypes = new();
public static TreeType GetTreeType(string name, string color, byte[] textureData)
{
string key = $"{name}_{color}";
if (!_treeTypes.TryGetValue(key, out var treeType))
{
treeType = new TreeType(name, color, textureData);
_treeTypes[key] = treeType;
}
return treeType;
}
public static int GetTotalFlyweights() => _treeTypes.Count;
}3. Define the Context Class (Extrinsic State)
The Tree class represents the individual instance
containing only the unique coordinates and a reference to the shared
TreeType.
public class Tree
{
private readonly int _x;
private readonly int _y;
private readonly TreeType _type;
public Tree(int x, int y, TreeType type)
{
_x = x;
_y = y;
_type = type;
}
public void Draw()
{
_type.Draw(_x, _y);
}
}4. Client Usage
The client code can instantiate thousands of Tree
objects without duplicating heavy asset buffers.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var forest = new List<Tree>();
byte[] sharedOakTexture = new byte[1024 * 1024]; // 1 MB texture
// Create 100,000 Oak trees
for (int i = 0; i < 100_000; i++)
{
TreeType oakType = TreeFactory.GetTreeType("Oak", "Green", sharedOakTexture);
forest.Add(new Tree(i, i * 2, oakType));
}
Console.WriteLine($"Total trees created: {forest.Count}");
Console.WriteLine($"Total TreeType instances in memory: {TreeFactory.GetTotalFlyweights()}");
// Render sample tree
forest[0].Draw();
}
}When to Use This Pattern
- When an application instantiates a massive number of objects that degrade available RAM.
- When the majority of an object's state can be separated into constant, shared values.
- When object identity does not matter—multiple logical objects can reference the same physical memory reference without breaking application logic.