Click here to Skip to main content
Click here to Skip to main content

Objects With Customizable Properties

, 25 Apr 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
A class with customizable properties without using Reflection.

Introduction

Why did I write this? I was in need of a more or less dynamic class where I could customize properties by configuration.

Background

My first approach was the classical "use-a-dictionary", but this left me with the decision to using a string as the key (I'm staying with that) and... what type as value?

Using object as value could bypass strong typing and I would not like to lose it. Also, I needed a possibility to iterate through properties without using Reflection in the client, even to define some of the properties to be read-only.

Last but not least, it should be possible to gain the value of a fixed property defined by an interface (read and write).

All in all, pretty much like M. Fowler mentioned in his article "Dealing with Properties".

The Code Behind

The code is divided into two assemblies:

  • Interfaces to an object with customizable properties.
  • An implementation of the mentioned interface.

The interface is nothing fancy. Just a small interface to an object with properties.

public interface IObjectWithProperties {

    event EventHandler<SMoni.ObjectWithProperties.
              Interfaces.PropertyChangedEventArgs> PropertyChanged;

    object this[string Key_] { get; set; }

    T      getValueOf<T>(string Key_);
    object getValueOf(string Key_);

    void setValueOf(string Key_, object Value_);
    void setValueOf(KeyValuePair<string, object> KeyValuePair_);

    DictionaryEntry[] Properties { get; }

    bool canWrite(string Key_);

    bool hasProperty(string Key_);
    
    bool areTypesEqual(string Key_, object Value_);    

}

The method hasProperty could be used to make sure the property exists, and canWrite to check if the property is writable. areTypesEqual should be called to make sure that the type of the given key and the value are the same.

For getting values, there are three possible ways (OK... there's a fourth, but I will explain this one later) to obtain a value by a given key:

  • index
  • getValueOf method which returns an object
  • getValueOf method with a generic, which returns a value with the generic-type, if the type matches

The methods could also return the value of a fixed property.

For setting a value, there are three possible ways (this time, really only three):

  • index
  • setValueOf method with a KeyValuePair
  • setValueOf method with a straight key as String and a value as Object

With all set-methods, it is also possible to set a fixed property.

Further, the properties in dictionaries fire an event if they have been changed. Attention: "normal" properties are not covered.

Last but not least, it is possible to query all properties using Properties[].

To avoid showing/using "normal" properties in Properties[] and by get-methods, the attribute [DoNotShowInProperties] could be used. Either in an interface or in the class itself.

[DoNotShowInProperties]
public [...] PropertyWhichIsNotShownInProperties {
    get {
        [...]
    }
    set {
        [...]
    }
}

or:

interface IDoNotUseAllFixedProperties {
    [...]

    [DoNotShowInProperties]
    [...] PropertyWhichIsNotShownInProperties { get; set; }

    [...]    
}

The implementation is divided into initialization and handling.

The initialization-methods are protected, so they could only be called by descendants (never mind Reflection). It is possible to choose different types of initialization (I hope the signature is self-explaining).

protected virtual void initializeValueOf(String Key_, 
                                         Object Value_) {
    [...]
}

protected virtual void initializeValueOf(String  Key_, 
                                         Object  Value_, 
                                         Boolean CreateKeyValuePair_) {

    [...]
}

protected virtual void initializeValueOf(String  Key_, 
                                         Object  Value_, 
                                         Boolean CreateKeyValuePair_, 
                                         Boolean AsReadOnly_) {
    [...]        
}

It is also possible to initialize fixed-properties, if necessary. Also nothing too fancy.

The handling of the properties is a little bit more tricky, but all in all very straightforward.

Using the code

It is necessary to inherit a class from AbstractObjectWithProperties. An inherited class could have fixed properties or initialize different Key-Value pairs.

public class MyClass : AbstractObjectWithProperties {

    public MyClass() {

        this.initializeValueOf("ID",         Guid.NewGuid(), true, true);
        this.initializeValueOf("Property 1", String.Empty,   true);
        this.initializeValueOf("Property 2", String.Empty,   true);

    }
    
    public Guid ID {
        get { 
            return this.getValueOf<Guid>("ID"); 
        }
    }

    public String Property_1 {
        get {
            return this.getValueOf<String>("Property 1");
        }
        set {
            this["Property 1"] = value;
        }
    }

    public String Property_2 {
        get {
            return this.getValueOf<String>("Property 2");
        }
        set {
            this["Property 2"] = value;
        }
    }        
}

For examples, just look into the demo code.

Points of Interest

The first problem I encountered was that unfortunately there is no common method to parse values, but this would be necessary for initialization of values to provide type-safety, so I decided to create a common interface which would allow me to parse values without thinking about the conversion of a real type.

To create a converter or parser, you call the factory with the type you would like to convert. (Right now, there are only six converters available, but expanding should not be a problem.)

The code is included but not explained.

If you are using a different MEMBER_PREFIX than "_", you should change it in the class AbstractObjectWithProperties. Same for MEMBER_BINDINGFLAGS.

/// <summary>
/// Member-prefix should be customized... if necessary...
/// </summary>
protected static String       MEMBER_PREFIX = "_";
protected static BindingFlags MEMBER_BINDINGFLAGS = 
          BindingFlags.Instance | BindingFlags.NonPublic;

protected virtual FieldInfo getFieldInfo(String Key_) {

    FieldInfo   result      = null;
    Type        contextType = this.GetType();

    while ((contextType != null) && (result == null)) {
        result      = contextType.GetField(MEMBER_PREFIX + Key_, MEMBER_BINDINGFLAGS);
        contextType = contextType.BaseType;
    }

    return result;
}

There are some drawbacks. I would especially recommend the article of M. Fowler: Dealing with Properties. This isn't a silver bullet, so don't use it as one.

  • Dynamic add or remove of a property isn't possible out-of-the-box... but could be added if required.
  • Properties not wrapping a field can not be used.
  • A property whose value is computed at runtime is not possible.
  • Casting is (currently) not possible (sorry, lack of an idea).

History

  • 25 April, 2011
    • Added a method to check equality of the type of a given key and a value.
  • 20 February, 2011
    • Attribute added to prevent showing fixed-properties.
    • Derive from MarshalByRefObject by preprocessor.
  • 05 September, 2010
    • Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

s_mon
Software Developer (Senior)
Germany Germany
- Born at the dawn of the Video Game Consoles
- Hacked his way through a C64 by Assembler
- Survived the hard times with an Amiga
- Got hit by Delphi on PC in the mid-90s
- Sinned with VB6
- Redeemed by C++ and C#

Comments and Discussions

 
GeneralInteresting approach PinmemberErion Pici28-Feb-11 21:59 
GeneralRe: Interesting approach Pinmembers_mon1-Mar-11 9:17 
GeneralMy vote of 1 PinmemberPaulo Zemek24-Feb-11 3:18 
GeneralRe: My vote of 1 Pinmembers_mon25-Feb-11 0:31 
Generaluserfull PinmemberPranay Rana24-Feb-11 3:02 
userfull

QuestionWhy not a dictionary? Pinmembermaspr7-Sep-10 23:57 
AnswerRe: Why not a dictionary? Pinmembers_mon8-Sep-10 9:33 
GeneralVery Good .. PinmemberSushant Joshi6-Sep-10 7:12 
GeneralRe: Very Good .. Pinmembers_mon7-Sep-10 6:43 
GeneralMy vote of 5 PinmemberEric Xue (brokensnow)5-Sep-10 12:03 
GeneralRe: My vote of 5 Pinmembers_mon5-Sep-10 21:24 
QuestionWhy? Pinmemberzlezj5-Sep-10 10:23 
AnswerRe: Why? PinmemberEric Xue (brokensnow)5-Sep-10 12:05 
AnswerRe: Why? Pinmembers_mon5-Sep-10 21:23 
GeneralRe: Why? PinmemberKubajzz8-Sep-10 11:28 
GeneralRe: Why? Pinmembers_mon14-Sep-10 22:47 
GeneralRe: Why? PinmemberJV999924-Feb-11 0:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411022.1 | Last Updated 25 Apr 2011
Article Copyright 2010 by s_mon
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid