StructuralC#verifiedVerified
Flyweight Pattern in C#
Minimizes memory usage by sharing fine-grained objects that represent repeated data, storing intrinsic state once and passing extrinsic state at call time.
How to Implement the Flyweight Pattern in C#
1Step 1: Flyweight stores shared (intrinsic) state
public class CharacterGlyph(char symbol, string fontFamily, int fontSize)
{
public char Symbol => symbol;
public string FontFamily => fontFamily;
public int FontSize => fontSize;
// Render with extrinsic state (position)
public string Render(int x, int y) =>
$"'{Symbol}' in {FontFamily} {FontSize}px at ({x},{y})";
}2Step 2: Factory ensures sharing of flyweight instances
public class GlyphFactory
{
private readonly Dictionary<string, CharacterGlyph> _cache = [];
public CharacterGlyph GetGlyph(
char symbol, string font, int size)
{
var key = $"{symbol}_{font}_{size}";
if (!_cache.TryGetValue(key, out var glyph))
{
glyph = new CharacterGlyph(symbol, font, size);
_cache[key] = glyph;
}
return glyph;
}
public int CacheSize => _cache.Count;
}
// Usage:
// var factory = new GlyphFactory();
// var a1 = factory.GetGlyph('A', "Arial", 12);
// var a2 = factory.GetGlyph('A', "Arial", 12);
// Console.WriteLine(ReferenceEquals(a1, a2)); // Trueusing System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
// [step] Flyweight for shared game particle configuration
public sealed record ParticleType(
string Name, string TextureId, string Color,
float BaseSize, float BaseSpeed)
{
// Intrinsic state shared across all particles of this type
}
// [step] Extrinsic state unique to each particle instance
public struct ParticleInstance
{
public float X;
public float Y;
public float VelocityX;
public float VelocityY;
public float LifetimeRemaining;
public int TypeIndex; // Index into the flyweight array
}
// [step] Thread-safe flyweight factory with statistics
public sealed class ParticleTypeFactory(
ILogger<ParticleTypeFactory> logger)
{
private readonly ConcurrentDictionary<string, (ParticleType Type, int Index)>
_types = new();
private readonly List<ParticleType> _typeList = [];
private long _cacheHits;
private long _cacheMisses;
public (ParticleType Type, int Index) GetOrCreate(
string name, string textureId, string color,
float baseSize, float baseSpeed)
{
return _types.GetOrAdd(name, _ =>
{
var type = new ParticleType(
name, textureId, color, baseSize, baseSpeed);
int index;
lock (_typeList)
{
index = _typeList.Count;
_typeList.Add(type);
}
Interlocked.Increment(ref _cacheMisses);
logger.LogDebug("Created particle type: {Name} (index {Index})",
name, index);
return (type, index);
});
}
public ParticleType GetByIndex(int index)
{
Interlocked.Increment(ref _cacheHits);
return _typeList[index];
}
public FlyweightStats GetStats() => new(
_types.Count,
Interlocked.Read(ref _cacheHits),
Interlocked.Read(ref _cacheMisses),
_types.Count * EstimateTypeSize());
private static long EstimateTypeSize() => 128; // Rough estimate in bytes
}
public record FlyweightStats(
int UniqueTypes, long CacheHits, long CacheMisses,
long EstimatedMemoryBytes)
{
public double HitRate => CacheHits + CacheMisses > 0
? (double)CacheHits / (CacheHits + CacheMisses) : 0;
}
// [step] Particle system uses flyweight for memory efficiency
public sealed class ParticleSystem(
ParticleTypeFactory factory,
ILogger<ParticleSystem> logger)
{
private readonly ParticleInstance[] _particles = new ParticleInstance[100_000];
private int _activeCount;
public int ActiveCount => _activeCount;
public void Emit(string typeName, float x, float y, int count)
{
var (_, typeIndex) = factory.GetOrCreate(
typeName, $"{typeName}_tex", "#FFFFFF", 1f, 100f);
for (var i = 0; i < count && _activeCount < _particles.Length; i++)
{
_particles[_activeCount++] = new ParticleInstance
{
X = x, Y = y,
VelocityX = Random.Shared.NextSingle() * 2 - 1,
VelocityY = Random.Shared.NextSingle() * 2 - 1,
LifetimeRemaining = 1f + Random.Shared.NextSingle(),
TypeIndex = typeIndex,
};
}
logger.LogDebug("Emitted {Count} particles of type {Type}",
count, typeName);
}
public void Update(float deltaTime)
{
for (var i = _activeCount - 1; i >= 0; i--)
{
ref var p = ref _particles[i];
var type = factory.GetByIndex(p.TypeIndex);
p.X += p.VelocityX * type.BaseSpeed * deltaTime;
p.Y += p.VelocityY * type.BaseSpeed * deltaTime;
p.LifetimeRemaining -= deltaTime;
if (p.LifetimeRemaining <= 0)
_particles[i] = _particles[--_activeCount];
}
}
}Flyweight Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Flyweight Pattern in the Real World
“A book publisher doesn’t print a separate metal typeface block for every letter ‘e’ on every page. Instead, one block for ‘e’ (intrinsic state) is reused in every position, with the printer supplying the ink color and position (extrinsic state) each time it is stamped. Thousands of impressions share one piece of metal.”