Skip to content

Symbol

Symbol is an interned string type optimized for fast equality comparison and memory efficiency when the same string values are used repeatedly.

Key Types

TypeDescriptionSource
SymbolInterned string wrapperSymbol.cs

Overview

Symbol wraps a string and interns it, ensuring that equal strings share the same memory and can be compared by reference.

Key features:

  • Fast equality: Reference comparison instead of character-by-character
  • Memory efficient: Identical strings share one allocation
  • Serializable: Full serialization support
  • Implicit conversion: Works seamlessly with strings

Why Use Symbol?

String Equality is Slow

cs
// String comparison: O(n) - compares each character
string a = "some_long_identifier";
string b = "some_long_identifier";
bool equal = a == b;  // Compares 20+ characters

Symbol Equality is Fast

cs
// Symbol comparison: O(1) - compares references
Symbol a = "some_long_identifier";
Symbol b = "some_long_identifier";
bool equal = a == b;  // Single reference comparison

Basic Usage

Creating Symbols

cs
// From string (implicit conversion)
Symbol sym1 = "my-identifier";

// Explicit creation
Symbol sym2 = new Symbol("my-identifier");

// Empty symbol
Symbol empty = Symbol.Empty;

Converting Back to String

cs
Symbol sym = "hello";

// Implicit conversion
string s1 = sym;

// Explicit property
string s2 = sym.Value;

Equality Comparison

cs
Symbol a = "test";
Symbol b = "test";
Symbol c = "other";

bool same = a == b;      // true (fast reference comparison)
bool different = a == c; // false

// Works with strings too
bool withString = a == "test";  // true

Interning Behavior

Symbols are automatically interned:

cs
Symbol a = "identifier";
Symbol b = "identifier";

// Same underlying string reference
object.ReferenceEquals(a.Value, b.Value);  // true

// GetHashCode is cached
int hash1 = a.GetHashCode();
int hash2 = b.GetHashCode();  // Same value, computed once

Serialization

Symbol supports all serialization formats:

cs
[DataContract, MemoryPackable, MessagePackObject]
public partial class MyEntity
{
    [DataMember, MemoryPackOrder(0), Key(0)]
    public Symbol Id { get; init; }

    [DataMember, MemoryPackOrder(1), Key(1)]
    public Symbol Category { get; init; }
}

Serialized as plain strings, but interned on deserialization.

Common Patterns

Dictionary Keys

cs
// Symbol keys are faster than string keys
private readonly Dictionary<Symbol, Handler> _handlers = new();

public void Register(Symbol eventType, Handler handler)
{
    _handlers[eventType] = handler;
}

public void Handle(Symbol eventType, Event e)
{
    if (_handlers.TryGetValue(eventType, out var handler))
        handler.Invoke(e);
}

Type Identifiers

cs
public class TypeRegistry
{
    private readonly Dictionary<Symbol, Type> _types = new();

    public void Register<T>(Symbol alias)
    {
        _types[alias] = typeof(T);
    }

    public Type? Resolve(Symbol alias)
    {
        return _types.GetValueOrDefault(alias);
    }
}

// Usage
var registry = new TypeRegistry();
registry.Register<User>("user");
registry.Register<Order>("order");

Type? type = registry.Resolve("user");

Enumeration-like Constants

cs
public static class EventTypes
{
    public static readonly Symbol Created = "created";
    public static readonly Symbol Updated = "updated";
    public static readonly Symbol Deleted = "deleted";
}

// Fast comparison
if (eventType == EventTypes.Created) {
    // Handle creation
}

Usage in Fusion

Symbol is used throughout Fusion for:

  • RPC method names: Fast routing lookup
  • Command types: Type identification
  • Cache keys: Dictionary key efficiency
  • Error codes: Fast error classification

Best Practices

Use for Repeated Comparisons

cs
// Good: Same strings compared many times
private readonly Symbol _expectedType = "expected";

public bool IsExpected(Symbol type)
{
    return type == _expectedType;  // Fast
}

// Less beneficial: One-time comparison
public bool Check(string input)
{
    Symbol sym = input;  // Interning cost
    return sym == "expected";  // One comparison doesn't amortize cost
}

Define Constants

cs
// Good: Define once, reuse
public static class Roles
{
    public static readonly Symbol Admin = "admin";
    public static readonly Symbol User = "user";
    public static readonly Symbol Guest = "guest";
}

// Use throughout application
if (user.Role == Roles.Admin) { }

Use for Dictionary Keys

cs
// Good: Fast lookups
Dictionary<Symbol, object> cache = new();

// Less optimal for strings that vary
// (each unique string is interned and stored)

Comparison with String Interning

FeatureSymbolstring.Intern()
Automatic interningYesManual call required
SerializationBuilt-inStandard string
Fast equalityYesOnly if both interned
Hash code cachingYesNo
Type safetyDistinct typeSame as string
Memory cleanupNever*Never

*Interned strings are never garbage collected in both cases.

Performance Considerations

When Symbol Helps

  • Same strings compared repeatedly
  • Dictionary keys with repeated lookups
  • Message routing by type/name
  • Configuration keys

When to Use Plain Strings

  • Unique/random strings (user input, UUIDs)
  • Short-lived comparisons
  • Large number of distinct values (memory concern)