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:
MyClass myObj = new MyClass();
AProperty<MyClass, string> myAProp = new AProperty<MyClass, string>(null);
myAProp.SetProperty(myObj, "Hello World");
GC.Collect();
string thePropValue = myAProp.GetProperty(myObj);
Console.WriteLine("The AProp Value is " + thePropValue);
myObj = null;
GC.Collect();
Console.WriteLine("before sleep");
Thread.Sleep(3000);
GC.Collect();
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();
Func<MyClass, string> stronglyTypedPropertyGetter =
CompiledExpressionUtils.GetCSPropertyGetter<MyClass, string>("MyProperty");
Console.WriteLine("\nTesting strongly typed property getter");
Console.WriteLine(stronglyTypedPropertyGetter(myTestObj));
Func<object, object> untypedPropertyGetter = myTestObj.GetUntypedCSPropertyGetter("MyProperty");
Console.WriteLine("\nTesting untyped property getter");
Console.WriteLine(untypedPropertyGetter(myTestObj));
Action<MyClass, string> stronglyTypedPropertySetter =
CompiledExpressionUtils.GetCSPropertySetter("MyProperty");
stronglyTypedPropertySetter(myTestObj, "Hi World");
Console.WriteLine("\nTesting strongly typed property setter");
Console.WriteLine(myTestObj.MyProperty);
Action<object, object> untypedPropertySetter = myTestObj.GetUntypedCSPropertySetter("MyProperty");
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>
{
event Action PropertyChangedEvent;
void TriggerPropertyChanged();
}
The target property setter can be represented by an even simpler interface that has only one method Set
:
public interface IPropSetter<PropertyType>
{
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");
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");
plainToPlainPropBinding.Bind();
Console.Write("Testing target property change after binding operation: ");
Console.WriteLine(targetObj.MyStringProp);
sourceObj.MyStringProp = "Hi World";
Console.Write("Testing target property change after the source property change: ");
Console.WriteLine(targetObj.MyStringProp);
#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>();
sourceObj = new MyTestDataClass();
targetObj = new MyTestDataClass();
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));
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");
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");
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.