Skip to content
Ioan Crișan edited this page Oct 23, 2020 · 2 revisions

Aims of dynamic expandable objects

  • Provide a dynamic behavior, so that, on the fly, new properties may be added to them without recompilation, just like in the dynamic languages like JavaScript or Python.
  • Access their properties:
    • directly, with the API defined by their class.
    • by the means of the dynamic keyword.
    • by their name, just like a dictionary.

The IIndexable interface

Objects supporting settings or getting values by the mean of a string key implement the IIndexable interface.

It provides one single indexer:

  • this[key: string]: object. By this mean, the object may be used like a dictionary.

The IExpando interface

Dynamic expandable objects implement the IExpando interface, which is an abstraction and an extension of the .NET's Expando sealed class. To support conversion to dynamic, it inherits the IDynamicMetaObjectProvider interface. Additionally, it inherits the IIndexable interface, to allow setting or getting property values by their name.

ExpandoBase

Kephas provides through ExpandoBase a base implementation for expando objects. Objects inheriting from an ExpandoBase can be used either as a standalone object, or can wrap an existing instance or dictionary and expose it as a dynamic object.

Dynamic value access.

To get or set a value from/to an ExpandoBase, based on the property name or key, the following steps are taken:

  • First of all, it is tried to identify a property from the inner object, if one is set, by the provided name/key.
  • The next try is to identify the property from the expando object itself.
  • Lastly, if still a property by the provided name cannot be found, the inner dictionary is searched/updated by the provided key.

Conversion to a dictionary

All the values from the expand can be collected in form a dictionary, using the ToDictionary() method. The values are added in their overwrite order:

  • First of all, all the inner dictionary entries are added.
  • Then, the property values in the current expand object are added or existing values overwritten.
  • Lastly, the values from the inner object are added or existing values overwritten.

Notes to inheritors

  • GetInnerObjectTypeInfo(): IRuntimeTypeInfo: returns the type information of the inner/wrapped object. If no wrapped object was provided, this returns null.
  • GetThisTypeInfo(): IRuntimeTypeInfo: returns the type information of this expando. Override if, for some reason, another type information should be used than the one of the expando itself.
  • TryGetValue(key: string, out value: object): boolean: Method used to get a value from the expando based on a property name or dictionary key. Override if a logic other than the default presented one should be used.
  • TrySetValue(key: string, value: object): boolean: Method used to set a value from the expando based on a property name or dictionary key. Override if a logic other than the default presented one should be used.

Expando

A ready-to-use expando class is Expando. Upon initialization, a flag controls how the internal dictionary is used: thread safe or not. Depending on it, the inner dictionary is set to a ConcurrentDictionary or Dictionary.

Examples

Plain expando

    dynamic expando = new Expando();

    expando.Property = "value";
    Assert.AreEqual("value", expando.Property);

Expando over a dictionary

    var dictionary = new Dictionary<string, object>();
    dynamic expando = new Expando(dictionary);

    expando.Property = "value";
    Assert.AreEqual("value", dictionary["Property"]);

Access over API, dynamic, or indexer

    public class Person : Expando
    {
        public int Age { get; set; }
    }

    //...
    
    var person = new Person();
    person.Age = 30;     // the age is set through the class API

    dynamic dynPerson = person;
    dynPerson.Age = 23;  // the age is set through the dynamic features
    Assert.AreEqual(23, person.Age);

    person["Age"] = 40;
    Assert.AreEqual(40, person.Age);

    dynPerson.IsOld = true;
    Assert.IsTrue(person["IsOld"]);

Expose non-dynamic objects as dynamic

    public class Contact
    {
        public string Name { get; set; }
    }

    //...
    
    var contact = new Contact();
    dynamic dynContact = new Expando(contact);

    dynContact.Name = "John";
    Assert.AreEqual("John", contact.Name);

Usage

Expandos can be sucessfully used where a dynamic context is useful. Examples:

  • Context objects, used to provide information about the current execution context, passed along the execution flow.
  • Metadata objects, used to provide meta information about entities and other artifacts.
  • Configuration settings.

Clone this wiki locally