Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

AProperties and Bindings outside of WPF Revisited

0.00/5 (No votes)
24 May 2013 1  
AProperties and Bindings outside of WPF Revisited

In the past I had a series of blog posts about re-implementing WPF concepts outside of WPF (see Codeproject: Binding without WPF, Codeproject: Attached Properties outside of WPF and Codeproject: Expression based Property Getters and Setters).

This post continues talking about non-WPF Attached Properties (AProperties) and Bindings (as well as the LINQ Expression property getters and setters) fixing problems left from the previous posts and preparing the readers for other interesting concepts that will to be expained the future articles.

Rearranging the Code

The new source code is located under BindingParadigmsCode.zip file. I rearranged the all the code related to the 3 blog posts mentioned above under the same project (namespace) NP.Paradigms. Some utility code I placed under NP.Paradigms.Extensions sub-namespace (sub-folder). Directory BindingParadigmsCode\TESTS contains the usage samples for the functionality.

AProperties without Memory Leaks

AProperties (attached properties implemented outside of WPF)
were introduced in Codeproject: Attached Properties outside of WPF.
In fact as described at the link above, the AProperties are, in many respects, more powerful than the regular WPF Attached Properties.

As a brief refresher, AProperties maps an object to some value by the object’s reference, so that the value can be retrieved given the object reference. Unlike the usual C# properties (and like the WPF’s Attached Properties), the AProperties do not need
to be defined on the object itself, instead, they are kind of externally attached to the object. Each AProperty has an internal map _objectToPropValueMap that maps the object’s reference to the corresponding AProperty value.

As one reader noticed, the AProperties might introduce a memory leak, in a sense that when all the outside references to the object that has some non-default AProperty value are removed, the _objectToPropValueMap dictionary within the corresponding AProperty might still hold a reference to the original object, so that the object is not garbage collected and the corresponding cell also stays within the _objectToPropValueMap dictionary. In fact the key of the map is the object itself, while the value (of type APropertyValueWrapper) has a reference Obj to the object.

In order to fix the memory leak, I replaced the value’s reference to the object by a WeakReference and replaced the Dictionary with ConditionalWeakTable class located within System.Runtime.CompilerServices namespace. ConditionalWeakTable class provides an implementation of Dictionary or Map with weak key references, allowing the garbage collector to collect the object and once the object is collected, it automatically removes it from the Map.

I tested performance of ConditionalWeakTable vs. usual C# Dictionary performance and found that the search and the insertion is approximately 1.4-1.6 times slower (which I deemed acceptable).

The code containing the AProperty garbage collection tests is located under CollectableAPropsTest project. Here is the body of the Main function with detailed comments:

 // create an object of MyClass class
MyClass myObj = new MyClass();

// create AProperty that assigns string to MyClass objects
AProperty<MyClass, string> myAProp = new AProperty<MyClass, string>(null);

myAProp.SetProperty(myObj, "Hello World");

// try to do garbage collection, 
// the myObj should not be collected at this point
// since the main program has a strong reference to it.
GC.Collect();

// the property should not be collected at this point
// since the reference to the object still exists in the 
// Main program.
string thePropValue = myAProp.GetProperty(myObj);

Console.WriteLine("The AProp Value is " + thePropValue);

// set the only 'strong' reference to myObj to null
myObj = null; 

// after the only 'strong' reference to myObj 
// was set to null, the call to 'GC.Collect()' should
// collect the object not-withstanding the fact
// that it is still weakly refenced from within myAProp object.
GC.Collect();
// destructor should be called before sleep or in the beginning of sleep;
Console.WriteLine("before sleep");
Thread.Sleep(3000);

GC.Collect();

// if you put a break point at the next line, 
// and expand the internals of myAProp object, 
// you'll see that it has no objects within it
// (the weak reference key has been removed)
Console.WriteLine("After sleep");

If you put a breakpoint at the last line and expand myAProp object, you will see that its _objectToPropValueMap does not contain any entries (its key and value counts are zero), meaning as the object had been collected, the corresponding map entry was removed also:

New Expression Based Property Getters and Setters

A blog post Codeproject: Expression based Property Getters and Setters talked about creating precompiled LINQ Expression based property getters and setters. They required the a-priory knowledge of the object and property types since they were returning Func<ObjectType, PropertyType> – for a getter and Action<ObjectType, PropertyType> – for a setter, with the requirement that the ObjectType and PropertyType should match the types of the object and property to which they are applied. Here I provided some extra methods where this requirement is relaxed – Func<object, object> is returned for a getter and Action<object, object> is returned for setter. This incurs an extra cast operation for a getter and two extra cast operations for a setter (the one for the object and for the property), but the expressions are still precompiled and the performance of the untyped lambdas is still very close to that of their strongly typed counterparts and greatly exceeds that of the reflection based functionality. The actual types of the untyped getters and setters can be inferred from the object and the property itself.

I placed this functionality under NP.Paradigms.Extensions namespace so that its extension methods will not pollute the main namespace.

To underscore that this functionality deals only with plain C# properties, I inserted CS within the function names.

The testing project for the functionality is called ExpressionCSPropertyGettersAndSettersTests and is located under TESTS folder. Here is the body of its Main method with the comments:

MyClass myTestObj = new MyClass();

// strongly typed property getter
Func<MyClass, string> stronglyTypedPropertyGetter =
    CompiledExpressionUtils.GetCSPropertyGetter<MyClass, string>("MyProperty");

// test strongly typed property getter (should return "Hello World")
Console.WriteLine("\nTesting strongly typed property getter");
Console.WriteLine(stronglyTypedPropertyGetter(myTestObj));

// get the untyped but compiled property getter (should be a little slower
// due to an extra cast operation, but still pretty close in perfromance
Func<object, object> untypedPropertyGetter = myTestObj.GetUntypedCSPropertyGetter("MyProperty");

// test the untyped property getter ( should return "Hello World")\\
Console.WriteLine("\nTesting untyped property getter");
Console.WriteLine(untypedPropertyGetter(myTestObj));


// strongly typed property setter
Action<MyClass, string> stronglyTypedPropertySetter =
    CompiledExpressionUtils.GetCSPropertySetter("MyProperty");

// set the property using strongly typed property setter
stronglyTypedPropertySetter(myTestObj, "Hi World");
Console.WriteLine("\nTesting strongly typed property setter");
Console.WriteLine(myTestObj.MyProperty);

// get the untyped by compiled property setter
Action<object, object> untypedPropertySetter = myTestObj.GetUntypedCSPropertySetter("MyProperty");

// use the untyped property setter to changed the property back to "Hello World"
untypedPropertySetter(myTestObj, "Hello World");
Console.WriteLine("\nTesting untyped property setter");
Console.WriteLine(myTestObj.MyProperty);

and here is the code for MyClass class:

public class MyClass
{
    public string MyProperty { get; set; }

    public MyClass()
    {
        MyProperty = "Hello World";
    }
}

New Property Binding Functionality

Non-WPF property and collection bindings is described at Codeproject: Binding without WPF blog post. The only properties we dealt with there, were plain C# properties that fire INotifyPropertyChanged.PropertyChanged event when modified. Now we also have AProperty concept and we want to bind them too. Moreover, sometimes we might want to bind a plain C# source property to an AProperty target and vice versa. Eventually we might also want to bind in a similar ways to WPF Attached Properties. Because of this new complexity, we have to take a different look at the property bindings.

As was shown at the previous binding blog post, the binding should implement IBinding interface:

public interface IBinding
{
    void InitialSync();
    void Bind(bool doInitialSync = true);
    void UnBind();
}

Let us take a look at the property binding from a different angle. A binding should be able to detect when the bound source property changes on the source object, get its value, possibly convert it to the type appropriate for the target property and set it on the target property of the target object. On top of this, when the binding is set, it would be logical to propagate the source property to the target property even though the source property did not change. It is logical to assume that the binding consists of 3 parts – property getter, property setter and property value converter. Property getter is an object of IPropGetter<PropertyType> interface that fires an event when the property changes that has the new property value as an argument. Also to cover the case of setting the target property value at the time when the binding is set (without the source property change) it has to have a method that would trigger the property propagation whenever the binding implementation needs it:

public interface IPropGetter<PropertyType>
{
    // fires when the property changes
    // its argument is new property value
    event Action PropertyChangedEvent;

    // forces PropertyChangedEvent to fire
    // (it is needed e.g. when when two properties
    // are bound - the source property should 
    // trigger the target property change even
    // if the source property does not change)
    void TriggerPropertyChanged();
}  

The target property setter can be represented by an even simpler interface that has only one method Set:

public interface IPropSetter<PropertyType>
{
    // sets the target property
    void Set(PropertyType property);
} 

The converter is represented by IValConverter interface unchanged from the previous article:

public interface IValConverter<InputType, OutputType>
{
    OutputType Convert(InputType sourceObj);
}

OneWayProperytBindingBase class combines the property getter, setter and converter. Its Bind function binds the source property getter and target property setter. Note, that the property getter and setter within OneWayPropertyBindingBase class do not specify any particular implementation – they are interfaces that can be implemented for plain C# properties or AProperties.

The property getter and setter for plain C# properties are located under PlainPropGetterAndSetter.cs file and they are expression based, while AProperty getters and setters are defined under APropsGetterAndSetter.cs file. By combining the correct getter and setter types, one can bind plain C# property to another plain C# property or to an AProperty or vice versa – an AProperty to another AProperty or to a plain C# property. There is a utility class BindingPath (named like that after WPF’s PropertyPath) that facilitates resolving the getter and setter types.

OneWayPropertyBinding class extends OneWayPropertyBindingBase class and utilizes the BindingPath objects to figure out its property getter and setter.

BindingTests project illustrates using the binding functionality connecting any combinations of plain C# properties and AProperties. The source and target objects are both of class MyTestDataClass that implements INotifyPropertyChanged interface and contains MyStringProp string property that fires the PropertyChanged event when it changes. The Main function’s code shows how to bind plain to plain, plain to AProperty, AProperty to plain and AProperty to AProperty. In each of these 4 cases, the binding sets the target property to be the same as the source property (“Hello World”) and then when the source property changes to “Hi World” the target property changes too. Here is the console output of the test run:

Testing binding from plain property to another plain property

Testing target property change after binding operation: Hello World
Testing target property change after the source property change: Hi World


Testing binding from plain property to another plain property

Testing target property change after binding operation: Hello World
Testing target property change after the source property change: Hi World


Testing binding from plain property to AProp

Testing target property change after binding operation: Hello World
Testing target property change after the source property change: Hi World


Testing binding from AProp to plain property

Testing target property change after binding operation: Hello World
Testing target property change after the source property change: Hi World
Press any key to continue . . .

Here is the Main method code:

#region Plain C# to Plain C# property binding 
Console.WriteLine("\n\nTesting binding from plain property to another plain property\n");

// initialize test objects
MyTestDataClass sourceObj = new MyTestDataClass
{
    MyStringProp = "Hello World"
};

MyTestDataClass targetObj = new MyTestDataClass();

OneWayPropertyBinding<string, string> plainToPlainPropBinding = 
    new OneWayPropertyBinding<string, string>();

plainToPlainPropBinding.SourceObj = sourceObj;
plainToPlainPropBinding.SourcePPath = new BindingPath<string>("MyStringProp");
plainToPlainPropBinding.TargetObj = targetObj;
plainToPlainPropBinding.TargetPPath = new BindingPath<string>("MyStringProp");

// bind the two properties. 
plainToPlainPropBinding.Bind();

// verify that the binding changed the target property 
// to be the same as the source property
Console.Write("Testing target property change after binding operation: ");
Console.WriteLine(targetObj.MyStringProp); // should print Hello World;

// let us change the source property and verify that target property also changes
sourceObj.MyStringProp = "Hi World";
Console.Write("Testing target property change after the source property change: ");
Console.WriteLine(targetObj.MyStringProp); // should print Hi World;

#endregion Plain C# to Plain C# property binding 

#region AProperty to AProperty binding
Console.WriteLine("\n\nTesting binding from plain property to another plain property\n");

AProperty<object, string> myAProperty = new AProperty<object, string>();

// reinitialize test objects
sourceObj = new MyTestDataClass();
targetObj = new MyTestDataClass();

// set AProperty on the source object before the binding 
myAProperty.SetProperty(sourceObj, "Hello World"); 


OneWayPropertyBinding<string, string> aPropToAPropBinding = new OneWayPropertyBinding<string, string>();

aPropToAPropBinding.SourceObj = sourceObj;
aPropToAPropBinding.SourcePPath = new BindingPath<string>(myAProperty);
aPropToAPropBinding.TargetObj = targetObj;
aPropToAPropBinding.TargetPPath = new BindingPath<string>(myAProperty);

aPropToAPropBinding.Bind();

Console.Write("Testing target property change after binding operation: ");
Console.WriteLine(myAProperty.GetProperty(targetObj));

// change the source property 
myAProperty.SetProperty(sourceObj, "Hi World");

Console.Write("Testing target property change after the source property change: ");
Console.WriteLine(myAProperty.GetProperty(targetObj));

#endregion AProperty to AProperty binding


#region plain property to AProperty binding

Console.WriteLine("\n\nTesting binding from plain property to AProp\n");

// reinitialize test objects
sourceObj = new MyTestDataClass
{
    MyStringProp = "Hello World"
};

targetObj = new MyTestDataClass();

OneWayPropertyBinding<string, string> plainToAPropBinding = new OneWayPropertyBinding<string, string>();

plainToAPropBinding.SourceObj = sourceObj;
plainToAPropBinding.SourcePPath = new BindingPath<string>("MyStringProp");
plainToAPropBinding.TargetObj = targetObj;
plainToAPropBinding.TargetPPath = new BindingPath<string>(myAProperty);

plainToAPropBinding.Bind();

Console.Write("Testing target property change after binding operation: ");
Console.WriteLine(myAProperty.GetProperty(targetObj));

sourceObj.MyStringProp = "Hi World";
Console.Write("Testing target property change after the source property change: ");
Console.WriteLine(myAProperty.GetProperty(targetObj));

#endregion plain property to AProperty binding

#region AProperty to plain property binding

Console.WriteLine("\n\nTesting binding from AProp to plain property\n");


// reinitialize test objects
sourceObj = new MyTestDataClass();
targetObj = new MyTestDataClass();

myAProperty.SetProperty(sourceObj, "Hello World");


OneWayPropertyBinding<string, string> aPropToPlainBinding = new OneWayPropertyBinding<string, string>();
aPropToPlainBinding.SourceObj = sourceObj;
aPropToPlainBinding.SourcePPath = new BindingPath<string>(myAProperty);
aPropToPlainBinding.TargetObj = targetObj;
aPropToPlainBinding.TargetPPath = new BindingPath<string>("MyStringProp");

aPropToPlainBinding.Bind();

Console.Write("Testing target property change after binding operation: ");
Console.WriteLine(targetObj.MyStringProp);

myAProperty.SetProperty(sourceObj, "Hi World");

Console.Write("Testing target property change after the source property change: ");
Console.WriteLine(targetObj.MyStringProp);

#endregion AProperty to plain property binding

You can see that our binding functionality is, in some respect, more generic than that of WPF – indeed in WPF only Attached (or Dependency) Property can be a target of a binding, while, in our case, it can be either plain C# property or AProperty. It the future I plan to generalize it even further, allowing binding to and from WPF Attached Properties.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here