I need to be able to monitor a large number of objects for changes. In nearly any case, I could just use INotifyPropertyChanged
and call it a day. However, my situation isn't as simple. The goal of my project is to create a simulation where any object can be plugged in, monitored, and accessed.
The plugging in is done via reflection using a Fluent API that designates properties and fields to include in the simulation's environment. It looks something like this:
public void DeclareVariables( IVariableBuilder builder )
{
builder.Include( x => x.PropertyA );
builder.Include( x => x.FieldA );
}
The properties and fields are then wrapped in a Variable
class and stored in an Environment
class.
public class Variable<T> : IVariable
{
private FieldInfo _field; // Properties are turned into FieldInfo via k__BackingField
private object _obj;
public string Id
{
get => $"{_obj.GetType}-{_field.Name}";
}
public T Value
{
get => _field.GetValue( _obj );
}
}
public class Environment
{
private Dictionary<string, IVariable> _variables;
}
This properly stores references to all of the fields and properties I am trying to include. However, I want to be able to monitor all of these for changes, and most of the included fields are either closed-source classes or primitives, both of which do not implement INotifyPropertyChanged` or cannot be derived.
I come from a JavaScript background, where you can accomplish this by creating an object proxy that can override property getters and setters, and since JavaScript is duck-typed, you can just replace the actual object instance in-place with the proxy.
Is there a way to do the same in C#? I'd imagine C#'s type safety rules would not allow a practice like this, especially with primitives. As far as I am aware, however, the only way to monitor these classes would be to somehow intercept operations where the value is being set.
Put short, it's not possible for arbitrary objects.
When you do this in JavaScript, you can:
Option one can't be done in C# for arbitrary objects.
Going for option two you can wrap an arbitrary instance with a proxy object with custom change detection logic. However, there are some caveats:
YourCustomClass
which you want to wrap. The proxy class, e.g. YourCustomClassProxy
doesn't share a common ancestor in the hierarchy other than System.Object, so you will not be able to substitute instances of YourCustomClass
with instances of YourCustomClassProxy
in method calls, etc.
So while there are mature libraries for creating dynamic proxies (see Castle Project), they support interfaces and virtual members, because they can be intercepted in runtime without violating type contracts. In theory it's possible to rewrite IL to add behavior you expect even to closed classes, but I wouldn't call it practical.
Summarizing, strategies available are:
INotifyPropertyChanged
from those that do not, and apply changes through proxies only