C# – Implementing a generic/dynamic custom property system in C#

cdynamicgenericsnet

I have an architecture design problem which I think is appropriate for this site.

Note that I have made an EDIT to this post below, reflecting my latest potential solution to this problem.

General problem description:

My primary goal is to design a software library/program (C#) to automate a very particular type of microscopy experiments. Our experimental setups consists of many distinct hardware devices that generally fall into a limited number of categories:

  • Point detector
  • XY sample stage
  • Autofocus device

The natural choice is to design an interface IDevice with some basic properties shared by all devices such as e.g. IDevice.Initialize(), IDevice.Name, …

We can even design an abstract class, inheriting IDevice, e.g. DeviceBase implementing some functionalty common to all devices.

Likewise, we can also design interfaces for specific devices, each time implementing the common stuff (IXYSampleStage might hold e.g. IXYSampleStage.Move(posX, posY) or IXYSampleStage.AxisXStepSize).

Ultimately, I can implement all these interfaces in the final classes representing specific devices. I think I know what to do there.

However, not all devices of a class are exactly the same. Some manufacturers offer rich functionality on top of the standard stuff. An XY Stage might have e.g. any number of optional/non-standard parameters to set such as e.g. channel delays, PID control values or whatever.

To tackle this,

I need a mechanism to add additional properties (not methods,
properties will suffice) to specific device classes. Higher level
code, interacting with the devices should understand these added
properties, be able to get/set them, however many there might be.

So much for the basic design problem.

What exists already

There is an OS software program, Micro Manager, which has such a system implemented, be it in C++ (full source here and no, I cannot use this SW directly for my purposes):

class PropertyBase
{
public:
   virtual ~PropertyBase() {}

   // property type
   virtual PropertyType GetType() = 0;

   // setting and getting values
   virtual bool Set(double dVal) = 0;
   virtual bool Set(long lVal) = 0;
   virtual bool Set(const char* Val) = 0;

   virtual bool Get(double& dVal) const = 0;
   virtual bool Get(long& lVal) const = 0;
   virtual bool Get(std::string& strVal) const = 0;

   // Limits
   virtual bool HasLimits() const = 0;
   virtual double GetLowerLimit() const = 0;
   virtual double GetUpperLimit() const = 0;
   virtual bool SetLimits(double lowerLimit, double upperLimit) = 0;

   // Some more stuff left out for brevity
   ...
};

Subsequently, the Property class implements some of the pure virtual functions related to the limits.

Next, StringProperty, IntegerProperty and FloatProperty implement the Get()/Set() methods. These kinds of properties are defined in an enum which allows only these three kinds of property.

Properties can next be added to a PropertyCollection which is basically a kind of dictionary that is part of a device class.

This all works nicely. We use MM a lot in the lab for other types of experiments but when trying to do similar things in my C# solution I stumbled about a few more fundamental questions. Most of these have to do with not being quite sure how to leverage specific features offered by C#/.NET (generic but definitely also dynamics, …) to perhaps improve the existing C++ implementation.

The questions:

First question:

To limit the types of properties allowed, MM defines an enum:

    enum PropertyType {
      Undef,
      String,
      Float,
      Integer
    };

Whereas C# has reflection and a lot of built-in support for types/generics. I therefore considered this:

public interface IProperty<T>
{
   public T value { get; set; }

   ...
}

I could next do an abstract PropertyBase<T> and ultimately

IntegerProperty : PropertyBase<int>
StringProperty : PropertyBase<string>
FloatProperty : PropertyBase<double>

But this gets me in trouble when trying to put them into a collection because in .NET following is not possible:

PropertyCollection : Dictionary<string, IProperty<T>>

Unless, perhaps, I adopt this strategy, i.e. have a non generic base class that only returns a type and then yet more base classes to actually implement my property system. However, this seems convoluted to me. Is there a better way?

I think that utilising the generic language features might allow me to do away with needing all these Get()/Set() methods in the MM example above, as I would be able to know the backing type of my property value.

I realise this is a very broad question but, there is a big gap between understanding the basics of some language features and being able to fully make the correct design decisions from the outset.

Second question

I am contemplating making my final device classes all inherit from a kind of DynamicDictionary (e.g. similar to the one from here):

    public class DynamicDictionary : DynamicObject
    {
        internal readonly Dictionary<string, object> SourceItems;

        public DynamicDictionary(Dictionary<string, object> sourceItems)
        {
            SourceItems = sourceItems;
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (SourceItems.ContainsKey(binder.Name))
            {
                SourceItems[binder.Name.ToLower()] = value;
            }
            else
            {
                SourceItems.Add(binder.Name.ToLower(), value);
            }
            return true;
        }
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (SourceItems != null)
            {
                if (SourceItems.TryGetValue(binder.Name.ToLower(), out result))
                {
                    return true;
                }
            }
            result = null;
            return true;
        }
    }
}

but instead of <string, object> I would then like to use <string, MyPropertyBase> where the string would be the alias for my property (which would correspond to the design choice from Q1).

Would doing such a thing make sense from a design standpoint?

I would actually only need to add custom properties to devices at design time (i.e. when writing a custom device driver). At runtime, I would only need to inspect my custom properties or get/set their values.

the C++ MM solves this by using a bunch of CreateProperty("alias", Property) methods in the device constructor, which again, works so maybe using dynamics here is overkill. But again, I am wondering if somebody could provide insight in possible advantages but certainly also disadvantages of going down the dynamic route.

To summarize

Are there .NET features (4.5) which I could leverage to implement some of the MM concepts in a more elegant way with a focus on the above mentioned specific matters?

Again, I do realise this is a very broad question and perhaps some of the possible answers are more a matter of taste/style than anything els but if so (this making the question unsuited for this platform), please provide feedback/comments such that I can adjust accordingly.

EDIT

I have spent some more thinking about this and tried to solve it in the following way:

First, an enum specifying the allowed propertytypes:

public enum PropertyType
{
    Undefined,
    String,
    Float,
    Integer
}

Second, an interface IPropertyType that is responsible for checking values fed to a property of a specific type:

public interface IPropertyType
{   
    Type BackingType { get; }

    PropertyType TypeAlias { get; }

    bool IsValueTypeValid(object value);
}

Then, a base class implementing IPropertyType:

public bool IsValueTypeValid(object value)
    {
        if (value == null)
        {
            return true;
        }

        Type type = value.GetType();

        if (type == this.BackingType)
        {
            return true;
        }

        return false;
    }

...

public Type BackingType
    {
        get
        {
            switch (this.TypeAlias)
            {
                case PropertyType.Integer:
                    return typeof(long);
                case PropertyType.Float:
                    return typeof(double);
                case PropertyType.String:
                    return typeof(string);
                default:
                    return null;
            }
        }
    }

I then have three IPropertyType: StringPropertyType, IntegerPropertyType and FloatPropertyType where each time, the property PropertyTypeAlias is set to one of the enum values.

I can now add my IPropertyType to IProperty types:

public interface IProperty
{
    string Alias { get; }

    /// If we want to limit the property to discrete values.
    Dictionary<string, object> AllowedValues { get; }

    bool HasLimits { get; }

    bool HasValue { get; }

    bool IsReadOnly { get; }

    /// Callback for HW operations using the property value.
    Func<PropertyFuncType, bool> PropertyFunction { set; }

    PropertyType TypeAlias { get; }

    object Value { get; set; }

    void AddAllowedValue(string alias, object value);

    void ClearAllowedValues();

    // On numeric properties, it might be usefull to limit the range of values.
    void SetLimits(object lowerLimit, object upperLimit);

    // These will do stuff on the HW...
    bool TryApply();

    bool TryUpdate();
}

Here, the Value setter will run the validation on input value as defined by the IPropertyType.

IProperty is almost fully implemented in PropertyBase which also has a field of IPropertyType which gets set upon instance creation (and cannot be changed thereafter; a property cannot change type.

From the base class e.g.:

    public object Value
    {
        get
        {
            return this.storedvalue;
        }

        set
        {
            if (this.propertyType.IsValueTypeValid(value) && this.IsValueAllowed(value))
            {
                this.storedvalue = value;
            }
        }
    }

That only leaves specific IProperty classes: StringProperty, IntegerProperty and FloatProperty where there is only one override, SetLimits() because it relies on comparison of values. Everything else can be in the base class:

public override void SetLimits(object lowerlimit, object upperlimit)
    {
        // Is false by default
        this.HasLimits = false;

        // Makes no sense to impose limits on a property with discrete values.
        if (this.AllowedValues.Count != 0)
        {
            // If the passed objects are in fact doubles, we can proceed to check them.
            if (this.IsValueTypeValid(lowerlimit) && this.IsValueTypeValid(upperlimit))
            {
                // In order to allow comparison we need to cast objects to double.
                double lowerdouble = (double)lowerlimit;
                double upperdouble = (double)upperlimit;

                // Lower limit cannot be bigger than upper limit.
                if (lowerdouble < upperdouble)
                {
                    // Passed values are OK, so set them.
                    this.LowerLimit = lowerdouble;
                    this.UpperLimit = upperdouble;

                    this.HasLimits = true;
                }
            }
        }
    }

This way, I think I have a system that allows me to have a collection of IProperty that can actually hold data of different types (int, float or string) where for each type of IProperty I have the ability to implement value limits etc (i.e. stuff that is different, depending on the data type)…

However, I still have the impression that there are either better ways to do this sort of thing or that I am over engineering things.

Therefore, any feedback on this is still welcome.

EDIT 2

After more searching online I believe the last bits of code posted here in the previous edit are leaning towards the Adaptive Object Model "pattern" (if it really is that).

However, most resources on this topic seem to be quite old and I'm wondering if there are now better ways to achieve these types of things.

Best Answer

You're making a type system on top of the existing type system. There is very little benefit to strongly-typed properties that cannot be referenced in compiled code. I have seen this approach used before and it ends up being a mess.

Just use a key-value store in a single property called ExtendedProperties or some such.

In other words, you are trying to avoid Dictionary<string, object>, but in that effort you are re-inventing the .Net type system.

Attach a property called ExtendedProperties of type Dictionary<string, object> and be done with it. The limitations of loosely-typed data are less costly than the road you're going down.

Related Topic