Skip to content

Instantly share code, notes, and snippets.

@IanSavchenko
Last active January 1, 2018 19:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IanSavchenko/d32ec4024f0b1a97e33874ff5d4c214c to your computer and use it in GitHub Desktop.
Save IanSavchenko/d32ec4024f0b1a97e33874ff5d4c214c to your computer and use it in GitHub Desktop.
Monitorable C#

Monitorable for C#

Sometimes (pretty often) there is a need to have a class with properties which can have value and a coupled event for when this value changes. Monitorable allows to skip a routine of creating a readonly property with an event. Repeating code like this:

class MyClass
{
    private int _property = 0;

    public int Property
    {
        get
        {
            return _property;
        }

        private set
        {
            if (_property == value)
                return;

            _property = value;
            PropertyUpdated(this, EventArgs.Empty);
        }
    }
}

can be rewritten like this:

public class MyBetterClass
{
    // Exposes getter-setter property _property
    private readonly MonitorableSource<int> _property = new MonitorableSource<int>();

    // Exposes getter-only property Property.Value and event Property.Updated
    public Monitorable<int> Property => _property.Monitorable; 
}

The end result is not absolutely equal because you need to use .Value to get property value, but it allows to save a lot of typing and repeating code.

public class MonitorableSourceExample
{
private readonly MonitorableSource<int> _a;
private readonly MonitorableSource<int> _b;
private readonly MonitorableSource<int> _c;
public MonitorableSourceExample()
{
_a = new MonitorableSource<int>();
_b = new MonitorableSource<int>();
_c = new MonitorableSource<int>();
}
public Monitorable<int> A => _a.Monitorable;
public Monitorable<int> B => _b.Monitorable;
public Monitorable<int> C => _c.Monitorable;
}
public class MonitorableUsingExample
{
private readonly MonitorableSourceExample _something;
public MonitorableUsingExample()
{
_something = new MonitorableSourceExample();
_something.A.Updated += (sender, args) =>
{
// do something on A changing
};
}
public int ADouble => _something.A.Value * 2;
}
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace IanSavchenko.Tools
{
/// <summary>
/// Represents a source for monitorable object - something that has a value and can change later
/// </summary>
public class MonitorableSource<T>
{
private T _value;
public MonitorableSource()
{
Monitorable = new Monitorable<T>(this);
}
public Monitorable<T> Monitorable { get; }
public T Value
{
get { return _value; }
set
{
var prevValue = _value;
_value = value;
if (!EqualityComparer<T>.Default.Equals(prevValue, value))
Updated?.Invoke(this, new MonitorableUpdatedEventArgs<T>(value));
}
}
public event EventHandler<MonitorableUpdatedEventArgs<T>> Updated;
public static explicit operator MonitorableSource<T>(T value)
{
return new MonitorableSource<T>() { _value = value };
}
}
/// <summary>
/// Represents a read-only object which value can be accessed and value changed noitifications can be subscribed to
/// </summary>
public class Monitorable<T>
{
private readonly MonitorableSource<T> _source;
public Monitorable(MonitorableSource<T> source)
{
_source = source;
}
public T Value => _source.Value;
public event EventHandler<MonitorableUpdatedEventArgs<T>> Updated
{
add { _source.Updated += value; }
remove { _source.Updated -= value; }
}
}
/// <inheritdoc />
/// <summary>
/// Event arguments for monitorable updated
/// </summary>
/// <typeparam name="T"></typeparam>
public class MonitorableUpdatedEventArgs<T> : EventArgs
{
public MonitorableUpdatedEventArgs(T updatedValue)
{
UpdatedValue = updatedValue;
}
public T UpdatedValue { get; }
}
/// <summary>
/// Handy utils for monitorable
/// </summary>
public static class MonitorableExtensions
{
/// <summary>
/// Asynchronously waits for <paramref name="monitorable"/> to get a <paramref name="value"/>
/// </summary>
/// <returns>Returns a Task which is resolved when monitorable gets specified <paramref name="value"/></returns>
public static Task<T> WaitForValue<T>(this Monitorable<T> monitorable, T value, CancellationToken cancellationToken = default(CancellationToken))
{
var tcs = new TaskCompletionSource<T>();
cancellationToken.Register(() => tcs.TrySetCanceled());
var updatedDelegate = new EventHandler<MonitorableUpdatedEventArgs<T>>((sender, args) =>
{
if (EqualityComparer<T>.Default.Equals(args.UpdatedValue, value))
tcs.TrySetResult(value);
});
monitorable.Updated += updatedDelegate;
// when task resolved - unsubscribing
tcs.Task.ContinueWith(t => monitorable.Updated -= updatedDelegate);
if (EqualityComparer<T>.Default.Equals(monitorable.Value, value))
tcs.TrySetResult(value);
return tcs.Task;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment