8

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.

Kris
  • 81
  • 1
  • 1
  • 8
  • So, the point is to just get/set some device properties through GUI? Is there any "logic" that depends on specific device? – Euphoric Aug 29 '14 at 08:32
  • Just one little comment - you don't have to do the ContainsKey check - simply setting a key will create a new entry if necessary. – Den Aug 29 '14 at 08:33
  • 1
    The ExpandoObject might be worth looking at (no limits setting though): http://stackoverflow.com/questions/1653046/what-are-the-true-benefits-of-expandoobject – Den Aug 29 '14 at 08:36
  • @Euphoric that would indeed be the case. Until now, we relied on hard-coding everything, requiring a lot of code re-writing for the UI, a major pain. The design here would be complemented with a UI for all device properties that is created dynamically so to speak. Until now the dynamic properties would only be used to set HW properties. Common operations/logic are more or less standard across devices (move, zero, calibrate, ...). The approach is necessary because different manufacturers do some things differently in HW with more/less parameters. – Kris Aug 29 '14 at 08:36
  • @Den As I understood, the ContainsKey is there to check that an item with identical key does not already exist, requiring only value to be set instead of creating a new item. Or is even that redundant? – Kris Aug 29 '14 at 08:38
  • 1
    @Kris "You can also ... add new elements by setting the value of a key that does not exist ..." http://msdn.microsoft.com/en-us/library/9tee9ht2(v=vs.110).aspx – Den Aug 29 '14 at 12:40
  • It's very unclear to me what your problem actually is. If two classes are different enough that you can't abstract away their differences, then why try? Either way the higher-level code which has to be able to interact with the two classes needs to know about the differences, so why not just hand it the concrete classes? An interface is worthless its consumer has to know the details it hides – Ben Aaronson Sep 17 '14 at 14:04
  • @BenAaronson. I have to represent many HW devices in code. 99% of those devices is identical but most carry some additional properties (either int value or float or string) for which I can set different values in HW. I need a flexible system to set those properties on my SW representations of the HW, also taking into account I need to restrict the range of allowed values for numeric properties. I do not know how I could make my Q more descriptive of the issue. In any case, everything below "EDIT" in my Q seems to do what I want now but I was wondering if there would be better approaches still. – Kris Sep 17 '14 at 14:10
  • @Kris Right, what I'm saying is everything that's common, put that behind an interface. Everything that's not common, use the concrete class. – Ben Aaronson Sep 17 '14 at 14:11
  • @BenAaronson, I get your point of view but if I later want to inject my HW classes into other code, dedicated concrete classes with lots of custom properties are not nice. Therefore, I wanted to achieve a more general system where I could add custom properties in a standardized way (such that the code consuming my HW objects would be able to know about them). I am not the first to think of this as this reference, taken from my Q, seems to indicate: http://joeyoder.com/Research/metadata/WICSA3/ArchitectureOfAOMsWICSA3.pdf (e.g. fig 2) Anyhow, thanks for the feedback! – Kris Sep 17 '14 at 14:21
  • @Kris I guess the question is whether the consuming code needs to know how to interact with these custom properties at design time or runtime. If it's design-time (e.g. there's some custom logic for whatever the property represents) then I don't see any advantage to using an AOM approach. If it's runtime, like if values just get passed in from a GUI or config file, then this makes more sense. In that case, I'd suggest maybe using reflection to grab the properties and attributes to represent any metadata you need like limits – Ben Aaronson Sep 17 '14 at 14:33
  • @BenAaronson It would indeed be runtime: the SW should also allow for users to write their own device adapters (simply because we do not have all the HW yet) that plug into the SW so the main application cannot be designed to be aware of all pre-existing HW classes. – Kris Sep 17 '14 at 16:08
  • @Kris Well, that's design-time, but it's design-time of the plugins. If I have time I may write up an answer to try to be a bit clearer – Ben Aaronson Sep 17 '14 at 16:42
  • I remember such a system to be in [Open Inventor (pdf)](http://webdocs.cs.ualberta.ca/~graphics/books/mentor.pdf), or the free variant [Coin3D](https://bitbucket.org/Coin3D/coin/overview). They wrap the base types, fields, etc. Sensors allow to be notified on changes. I had a bit of fun with it in former times. – Stefan Hanke Sep 18 '14 at 07:26

5 Answers5

14

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.

Chris McCall
  • 564
  • 2
  • 8
5

If properties will only ever be used in GUI and as read/set for HW, then there is no need for generics or reflection. Just use simple OOP classes. What you should focus is not how internals of the properties will be, but on how they will be used. How do you want to have those properties editable from GUI or how you want to save or retrieve them to the HW device?

public interface PropertyBase
{
    string Name {get;} // name of this property

    void SetToDevice(); // sets the value to device
    void ReadFromDevice(); // read from device

    GUIEditor GetEditor(); // returns editor for this property
}

public class PropertyContainer // device can implement or contain this
{
    public List<PropertyBase> Properties;
}

public interface IntProperty : PropertyBase
{
    public IDevice Device {get;set;}
    public string Name {get;set;} // name of this property

    public int Value {get;set;}

    void SetToDevice()
    {
        Device.SetInt(Name, Value); 
    }
    void ReadFromDevice()
    {
        Value = Device.ReadInt(Name);
    }

    GUIEditor GetEditor()
    {
        return new IntEditor(this); // IntEditor can get/set value itself
    }
}

The GUIEditor will be way for GUI to know how to edit the specific property. If you have dependency problems, you could use Visitor pattern to move this logic into View. The major advantage of this approach is that you have are open to add new device-specific properties without having to change existing code.

Euphoric
  • 36,735
  • 6
  • 78
  • 110
  • This is the kind of feedback I am looking for. It indeed never occurred to me to have an approach where I focus on the consumption of the property much rather than the contents. I'm still trying to assess the impact of this solution but thanks already for the nice insight. – Kris Aug 29 '14 at 09:32
0

What you want pretty much sounds like WPF´s dependency properties and attached properties. You may want to look into the design of System.Windows.DependencyObject as a base class and System.Windows.DependencyProperty as the property class. It does involve a bit of overhead, but is really quite flexible.

  • After some more digging around the internet, I found that actually many Content Management Systems for websites such as e.g. Umbraco (www.umbraco.org) seem to use the approach I intended: Most CMS allow users to define custom document types (home page, text page, contact page, ...) with many common properties (URL, Id, guid, ...) but also custom properties (an intro text for home page, an address field for contact page, ...). Much of the stuff I edited into my Q at the end is based on what I learned from looking at such CMS systems, Umbraco in particular. – Kris Sep 17 '14 at 14:14
  • I think the WPF dependency properties system is more elaborate still... – Kris Sep 17 '14 at 14:15
  • This link is relevant btw (also amended to the question): http://joeyoder.com/Research/metadata/WICSA3/ArchitectureOfAOMsWICSA3.pdf – Kris Sep 17 '14 at 14:23
0

I had the same kind of issue. I have a "simple" plugin system where I have a workspace where i can drag and drop widgets in the form of plugin. I wanted to give flexibility (still working on the full functionality) to the plugin creator to add a set of properties attached to the plugin. As i'm using an MVP approach I created an interface to access the property

public interface IPluginControlSettingsPropertyAccessors<T>
{
    bool Validator(T _Source);

    bool Setter(T _Source);

    bool Getter(out T _Result);
}

and for each property the plugin exposes several accessors of different types with the constraint to at least expose a

IPluginControlSettingsPropertyAccessors<string> 

for each property.

Then it depends on the View to actually implement a way to handle certain types of properties e.g. you can have:

if (l_Property is IPluginControlSettingsPropertyAccessors<Enum>)
{
     View.AddEnumHandler(l_Property);
}
else if (l_Property is IPluginControlSettingsPropertyAccessors<String>)
{
    View.AddStringHandler(l_Property);
}

As you can see this disconnects an object and its properties from how they are being shown on the GUI. With this approach a simple GUI could simply treat all properties in their string form for visualization purposes and defer string handling to the property setter and validator. If an object has a property which exposes

IPluginControlSettingsPropertyAccessors<String>
IPluginControlSettingsPropertyAccessors<Color>

and your GUI only support the string type property then it will have a textbox which (depending on the string validator) might accept only colors in the #FFFFFF format. A more complex GUI might instead support IPluginControlSettingsPropertyAccessors and provide a color pick.

I am quite new to C# too and I could not come up with a better solution than this regarding an MVP pattern but so far it has proven quite easy to maintain.

0

You could use an ExpandoObject to contain your properties. This has the benefit of allowing you to use any property name and type at compile-time, so you can add them and read them as you see fit, as long as you can prevent yourself from getting confused. The down side is that you do not get the same static type checking that you would if you defined interfaces for all of your properties.

Here is an example where Foo and Bar are the dynamic, device-specific properties of devices.

First, define an IDevice interface that has your base properties:

public interface IDevice
{
    string Name { get; }
}

And an interface that exposes a dynamic to contain the properties:

public interface IPropertyContainer 
{
    dynamic Properties { get; }
}

Now tie them together in your base class:

public class DeviceBase : IDevice, IPropertyContainer
{
    public string Name { get; set; }

    protected dynamic _properties = new ExpandoObject();

    public dynamic Properties
    {
        get
        {
            return _properties;
        }
    }
}

When you define your specific device classes, populate the ExpandoObject with your dynamic properties:

public class MyDeviceA : DeviceBase
{
    public MyDeviceA()
    {
        _properties.Foo = "Foo";
    }
}

public class MyDeviceB : DeviceBase
{
    public MyDeviceB()
    {
        _properties.Bar = "Bar";
    }
}

Notice that nowhere do we declare that Bar and Foo are valid properties. It's inferred from the usage... which is both convenient and a little dangerous.

A test program:

public class Program
{
    public static void Main()
    {
        DeviceBase deviceA = new MyDeviceA();
        Console.WriteLine(deviceA.Properties.Foo);

        DeviceBase deviceB = new MyDeviceB();
        Console.WriteLine(deviceB.Properties.Bar);
    }
}

Actual output:

Foo
Bar

Here is a link to a working example on DotNetFiddle

John Wu
  • 26,032
  • 10
  • 63
  • 84