CodeJunkie.Observables 1.0.0

dotnet add package CodeJunkie.Observables --version 1.0.0
                    
NuGet\Install-Package CodeJunkie.Observables -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeJunkie.Observables" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeJunkie.Observables" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="CodeJunkie.Observables" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeJunkie.Observables --version 1.0.0
                    
#r "nuget: CodeJunkie.Observables, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeJunkie.Observables@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeJunkie.Observables&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=CodeJunkie.Observables&version=1.0.0
                    
Install as a Cake Tool

CodeJunkie.Observables

A lightweight and efficient observable data structures library for Unity and .NET, providing reactive programming capabilities with MVVM pattern support.

🎯 Features

  • Observable Properties - Strongly-typed properties with change notifications
  • Observable Collections - Lists and dictionaries with collection change events
  • Cross-Platform - Works with Unity (.NET 2.0/4.6) and modern .NET versions
  • Thread-Safe - All operations are thread-safe with proper locking mechanisms
  • Memory Efficient - Cached event args and optimized allocations
  • MVVM Ready - Full support for data binding patterns

📦 Core Components

ObservableProperty<T>

Provides property change notifications for individual values:

var health = new ObservableProperty<int>(100);
health.ValueChanged += (sender, e) => Console.WriteLine($"Health changed to: {health.Value}");
health.Value = 80; // Triggers ValueChanged event

ObservableList<T>

A dynamic list that provides notifications when items are added, removed, or changed:

var inventory = new ObservableList<string>();
inventory.CollectionChanged += (sender, e) => {
    Console.WriteLine($"Collection action: {e.Action}");
};

inventory.Add("Sword");        // Triggers Add event
inventory.Remove("Sword");     // Triggers Remove event
inventory.Clear();             // Triggers Reset event

ObservableDictionary<TKey, TValue>

A dictionary implementation with collection change notifications:

var playerStats = new ObservableDictionary<string, int>();
playerStats.CollectionChanged += (sender, e) => {
    Console.WriteLine($"Stats changed: {e.Action}");
};

playerStats["Strength"] = 15;  // Triggers Add event
playerStats["Strength"] = 20;  // Triggers Replace event

ObservableObject

Base class for creating observable view models:

public class PlayerViewModel : ObservableObject 
{
    private string _name;
    private int _level;
    
    public string Name 
    {
        get => _name;
        set => Set(ref _name, value); // Automatically raises PropertyChanged
    }
    
    public int Level 
    {
        get => _level;
        set => Set(ref _level, value);
    }
}

🎮 Unity Game Development Examples

Player Health System

public class PlayerHealth : MonoBehaviour 
{
    private ObservableProperty<int> _health = new ObservableProperty<int>(100);
    
    void Start() 
    {
        // Bind to UI
        _health.ValueChanged += OnHealthChanged;
    }
    
    void OnHealthChanged(object sender, EventArgs e) 
    {
        var healthBar = FindObjectOfType<Slider>();
        healthBar.value = _health.Value / 100f;
        
        if (_health.Value <= 0) 
        {
            GameOver();
        }
    }
    
    public void TakeDamage(int damage) 
    {
        _health.Value = Mathf.Max(0, _health.Value - damage);
    }
}

Inventory Management

public class InventoryManager : MonoBehaviour 
{
    private ObservableList<Item> _items = new ObservableList<Item>();
    
    void Start() 
    {
        _items.CollectionChanged += OnInventoryChanged;
    }
    
    void OnInventoryChanged(object sender, NotifyCollectionChangedEventArgs e) 
    {
        switch (e.Action) 
        {
            case NotifyCollectionChangedAction.Add:
                foreach (Item item in e.NewItems) 
                {
                    CreateItemUI(item);
                }
                break;
                
            case NotifyCollectionChangedAction.Remove:
                foreach (Item item in e.OldItems) 
                {
                    DestroyItemUI(item);
                }
                break;
        }
    }
    
    public void AddItem(Item item) => _items.Add(item);
    public void RemoveItem(Item item) => _items.Remove(item);
}

Game State Management

public class GameStateManager : ObservableObject 
{
    private GameState _currentState;
    private ObservableDictionary<string, int> _playerStats = new ObservableDictionary<string, int>();
    
    public GameState CurrentState 
    {
        get => _currentState;
        set => Set(ref _currentState, value);
    }
    
    public ObservableDictionary<string, int> PlayerStats => _playerStats;
    
    void Start() 
    {
        PropertyChanged += OnPropertyChanged;
        _playerStats.CollectionChanged += OnStatsChanged;
    }
    
    void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
    {
        if (e.PropertyName == nameof(CurrentState)) 
        {
            HandleStateChange();
        }
    }
    
    void OnStatsChanged(object sender, NotifyCollectionChangedEventArgs e) 
    {
        UpdateStatsUI();
    }
}

🔧 Advanced Usage

Custom Observable Properties

public class ClampedObservableProperty<T> : ObservableProperty<T> where T : IComparable<T>
{
    private T _min;
    private T _max;
    
    public ClampedObservableProperty(T value, T min, T max) : base(value)
    {
        _min = min;
        _max = max;
    }
    
    public override T Value 
    {
        get => base.Value;
        set 
        {
            var clampedValue = value;
            if (value.CompareTo(_min) < 0) clampedValue = _min;
            if (value.CompareTo(_max) > 0) clampedValue = _max;
            base.Value = clampedValue;
        }
    }
}

// Usage
var playerHealth = new ClampedObservableProperty<int>(100, 0, 100);
playerHealth.Value = 150; // Will be clamped to 100

Reactive Chains

public class PlayerExperience : ObservableObject 
{
    private int _experience;
    private int _level;
    
    public int Experience 
    {
        get => _experience;
        set 
        {
            if (Set(ref _experience, value)) 
            {
                UpdateLevel(); // Automatically update level when XP changes
            }
        }
    }
    
    public int Level 
    {
        get => _level;
        private set => Set(ref _level, value);
    }
    
    private void UpdateLevel() 
    {
        Level = Mathf.FloorToInt(_experience / 1000f) + 1;
    }
}

🛠️ Installation

Unity Package Manager

Add the following to your manifest.json:

{
  "dependencies": {
    "com.codejunkie.observables": "https://github.com/yourrepo/CodeJunkie.Observables.git"
  }
}

Manual Installation

  1. Download the source files
  2. Copy to your Unity project's Assets/Scripts/CodeJunkie/Observables/ folder
  3. Add the namespace: using CodeJunkie.Observables;

📋 Platform Support

  • Unity 2019.4+ (NET_2_0, NET_2_0_SUBSET, NET_4_6, NET_STANDARD_2_0)
  • .NET Framework 4.6+
  • .NET Core 2.0+
  • .NET 5/6/7/8

🔄 Thread Safety

All observable collections and properties are thread-safe:

// Safe to call from multiple threads
var sharedList = new ObservableList<int>();

Task.Run(() => sharedList.Add(1));
Task.Run(() => sharedList.Add(2));
Task.Run(() => sharedList.Clear());

📚 API Reference

ObservableProperty<T>

  • T Value - Gets or sets the property value
  • event EventHandler ValueChanged - Raised when value changes
  • implicit operator T - Implicit conversion to T
  • implicit operator ObservableProperty<T> - Implicit conversion from T

ObservableList<T>

  • void Add(T item) - Adds an item
  • void AddRange(IEnumerable<T> items) - Adds multiple items
  • bool Remove(T item) - Removes an item
  • void RemoveRange(int index, int count) - Removes multiple items
  • void Move(int oldIndex, int newIndex) - Moves an item
  • event NotifyCollectionChangedEventHandler CollectionChanged

ObservableDictionary<TKey, TValue>

  • void Add(TKey key, TValue value) - Adds a key-value pair
  • bool Remove(TKey key) - Removes a key-value pair
  • void AddRange(IDictionary<TKey, TValue> items) - Adds multiple pairs
  • event NotifyCollectionChangedEventHandler CollectionChanged

ObservableObject

  • bool Set<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
  • void RaisePropertyChanged([CallerMemberName] string propertyName = null)
  • event PropertyChangedEventHandler PropertyChanged

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by Microsoft's ObservableCollection and INotifyPropertyChanged patterns
  • Optimized for Unity game development workflows
  • Community feedback and contributions
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CodeJunkie.Observables:

Package Downloads
CodeJunkie.Repositories

"CodeJunkie.Repositories is a library for managing data repositories, providing a consistent interface for data access and manipulation in .NET applications."

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 212 6/7/2025