Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

How To Work Around Passing a Property by Reference

Rate me:
Please Sign up or sign in to vote.
4.00/5 (6 votes)
27 Nov 2008CPOL2 min read 74.7K   155   18   16
How to work around passing a property by reference using delegates and wrapper class.

Introduction

This article will explain how to work around passing a .NET property similar to passing by ref (or out) parameter. This will often be useful when you would like to alter the actual property pointer. This method is purely for the purpose of having this workaround and not for performance improvement. Since this method uses reflection, it will have some minor overhead.

I hope you will enjoy this article.

Background

When something is passed by ref, its pointer is passed so functions using it will have direct access to the pointer for alteration. The challenge is to pass a property by ref. However, properties are translated into two functions when compiled: the get function and set function. Unlike other functions, the framework does not expose these functions at design time which limits one from accessing their delegates. 

Reference

Thanks to Alois Kraus for the original idea:

Property Wrapper

In order to access the property by ref, one must attempt to access the get and set delegate. We can accomplish this by using reflection. The PropertyInfo class contains the GetValue, and SetValue functions which then can be wrapped in a function to simulate the get and set delegates. We can get this by calling the GetProperty function of a given type.

Consider the class:

C#
/// <summary>
/// Used as a wrapper class for passing a property
/// </summary>
/// <typeparam name="T">The type of the property return value</typeparam>
public class PropertyInvoker<T>
{
    private PropertyInfo propInfo;
    private object obj;

    /// <summary>
    /// Construct an property Invoker
    /// </summary>
    /// <param name="PropertyName">the property of an object we want to access</param>
    /// <param name="o">the object</param>
    public PropertyInvoker(string PropertyName, object o)
    {
        // set the instance object
        this.obj = o;
        // get the property information and check weather the type matches T
        this.propInfo = o.GetType().GetProperty(PropertyName, typeof(T));  
    }

    /// <summary>
    /// Wrapping the get and set into a property
    /// </summary>
    public T Property
    {
        get
        {
            return (T) propInfo.GetValue(obj, null);
        }
        set
        {
            propInfo.SetValue(obj, value, null);
        }
    }

    /// <summary>
    /// Wrapping the get function
    /// </summary>
    /// <returns></returns>
    public T GetProperty()
    {
        return this.Property;
    }

    /// <summary>
    /// Wrapping the set function
    /// </summary>
    /// <param name="value"></param>
    public void SetProperty(T value)
    {
        this.Property = value;
    }
}

The PropertyInvoker<T> class allows you to wrap a given property of an object, then you can pass this class into another function similar to by ref since you will now gain direct access to the property itself.

Examples

Consider the below class Person which contains properties that are objects, arrays, and primitive types.

C#
/// <summary>
/// describes a person
/// </summary>
public class Person
{
    // object
    protected String _fullName;
    protected Person _father;
    protected Person _mother;
    // primitive
    protected int _age;

    // arrays
    protected Person[] _children;

    public String FullName
    {
        get {
            return this._fullName;
        }
        set {
            this._fullName = value;
        }
    }

    public Person Father
    {
        get {
            return this._father;
        }
        set {
            this._father = value;
        }
    }

    public Person Mother
    {
        get {
            return this._mother;
        }
        set {
            this._mother = value;
        }
    }

    public int Age
    {
        get {
            return this._age;
        }
        set {
            this._age = value;
        }
    }

    public Person[] Children
    {
        get {
            return this._children;
        }
        set {
            this._children = value;
        }
    }

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="Father"></param>
    /// <param name="Mother"></param>
    /// <param name="FullName"></param>
    /// <param name="Age"></param>
    /// <param name="Children"></param>
    public Person(Person Father, Person Mother, 
		String FullName, int Age, params Person[] Children)
    {
        this._father = Father;
        this._mother = Mother;
        this._fullName = FullName;
        this._age = Age;
        this._children = Children;
    }

    /// <summary>
    /// Create a person
    /// </summary>
    /// <param name="FullName"></param>
    /// <param name="Age"></param>
    public Person(String FullName, int Age)
        :this(null, null, FullName, Age)
    {
    }
}

Normally, it is enough to pass the person object to perform functionalities on the person object. However, sometimes it is required to perform an action on a property rather than the object itself. This means that the function is performed on the Type of the property such as a String manipulation, or an Array manipulation.

Normal by ref requires the pointer of the array:

C#
..
// by ref resize of an array
Person[] children = new Person[] {new Person("Yang Yu", 30)};
// resize this array
Array.Resize<T>(ref children,2);
children[1] = new Person("Kendra Harwood", 28);
.. 

The above code has taken a normal array and resized it with by ref function which really constructs a new array and reassigns the pointer of children to that bigger array.

But if we want to perform this on the Children property of a Person, we really cannot pass the property in since it is an function, not a variable. This is where PropertyWrapper comes in handy.

C#
..

        public static void Resize<T>(PropertyInvoker<T[]> pArray, int newSize)
        {
            // pre:
            if (pArray == null) return;

            // get the property value
            T[] array = pArray.Property;
            // perform by ref functionalities
            Array.Resize<T>(ref array, newSize);
            // set the property
            pArray.Property = array;
        }
..
C#
Person me = new Person("Yang Yu", 30);
 me.Children = new Person[] {new Person("Raine Yu", 1)};
 // call the resize using a property wrapper on the children property
 Resize<Person>(new PropertyInvoker<Person[]>("Children", me), 2);
 // set resized array
 me.Children[1] = new Person("Josh Yu", 2);

get and set Delegate Wrapper

We can use the GetProperty and SetProperty function of PropertyInvoker as delegates:

C#
public delegate string GetDelegate();
public delegate void SetDelegate(string value);

Consider the following overloaded functions:

C#
..
        public static void ReverseString(PropertyInvoker<String> strProperty)
        {
            // overload with the get property delegate
            ReverseString(strProperty.GetProperty, strProperty.SetProperty);
        }

        public static void ReverseString(GetDelegate get, SetDelegate set)
        {
            // invoke the get property method
            String str = get.Invoke();
            // call actual str reverse function
            set.Invoke(ReverseString(str));
        }

        public static String ReverseString(string str)
        {
            if (str.Length < 2) return str;

            char start = str[0], end = str[str.Length-1];
            // reverse inner
            return string.Concat(end, ReverseString
			(str.Substring(1, str.Length - 2)), start);
        }
..

The overloaded ReverseString takes the delegates as a parameter, which we use the get property and set property of the PropertyInvoker object. For example:

C#
ReverseString(new PropertyInvoker<string>("FullName", new Person("Yang Yu", 33)));

Points of Interest

I hope this article has helped in your understanding of some design patterns around delegates and property passing as a parameter.

History

  • 27th November, 2008 - Article posted

License

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


Written By
Architect
Canada Canada
Engineer, maker, food lover

Comments and Discussions

 
GeneralSorry if I've missed something but... Pin
stodgey19-Dec-08 8:30
stodgey19-Dec-08 8:30 
GeneralRe: Sorry if I've missed something but... Pin
Yang Yu20-Dec-08 9:06
Yang Yu20-Dec-08 9:06 
GeneralRe: Sorry if I've missed something but... Pin
stodgey22-Dec-08 6:59
stodgey22-Dec-08 6:59 
GeneralMy vote of 1 Pin
gerippe28-Nov-08 6:28
gerippe28-Nov-08 6:28 
GeneralRe: My vote of 1 Pin
Yang Yu28-Nov-08 6:40
Yang Yu28-Nov-08 6:40 
QuestionHuh? Pin
PIEBALDconsult28-Nov-08 4:29
mvePIEBALDconsult28-Nov-08 4:29 
AnswerRe: Huh? Pin
Yang Yu28-Nov-08 6:44
Yang Yu28-Nov-08 6:44 
GeneralRe: Huh? Pin
PIEBALDconsult28-Nov-08 10:00
mvePIEBALDconsult28-Nov-08 10:00 
GeneralRe: Huh? Pin
Yang Yu28-Nov-08 13:02
Yang Yu28-Nov-08 13:02 
GeneralThis code is copied Pin
Alois Kraus27-Nov-08 23:04
Alois Kraus27-Nov-08 23:04 
GeneralRe: This code is copied Pin
Paw Jershauge28-Nov-08 1:56
Paw Jershauge28-Nov-08 1:56 
NewsRe: This code is copied Pin
Yang Yu28-Nov-08 4:01
Yang Yu28-Nov-08 4:01 
GeneralRe: This code is copied Pin
Alois Kraus28-Nov-08 7:38
Alois Kraus28-Nov-08 7:38 
GeneralMaybe it's just the example, but I'm not seeing the point... [modified] Pin
jimbobmcgee27-Nov-08 23:02
jimbobmcgee27-Nov-08 23:02 
GeneralRe: Maybe it's just the example, but I'm not seeing the point... Pin
Yang Yu28-Nov-08 4:11
Yang Yu28-Nov-08 4:11 
GeneralNice Pin
johannesnestler27-Nov-08 20:55
johannesnestler27-Nov-08 20:55 
Nice idea - nice implementation! Got my 5 Cool | :cool:

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

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