Building read-only objects in Xaml
We often use Xaml to instantiate and initialize objects. For example, given “<Foo Bar=’1’/>”, a Xaml loader creates a Foo object, and sets the Bar property to 1. That works when the Bar property is settable, but what can you do if it isn’t?
An example of this scenario in .Net today shows up with strings; the String type is immutable, it doesn’t have any settable properties. But sometimes you want to build a string with formats, concatenation, etc. Thus the invention of the StringBuilder class, where the properties have setters (and the methods can mutate an instance).
You can follow the same idea in Xaml by creating builder types, using markup extensions.
Here’s an example of a type whose properties can’t be set except internally, since it has read-only properties:
<Assembly: XmlnsDefinition("https://blogs.msdn.com/mikehillberg/BuilderPattern", "PeopleNamespace")>
Namespace PeopleNamespace
Public Class Person
Private _name As String
Public ReadOnly Property Name() As String
Get
Return _name
End Get
End Property
Private _age As Integer
Public ReadOnly Property Age() As Integer
Get
Return _age
End Get
End Property
Public Shared Function Create(ByVal Name As String, ByVal Age As Integer) As Person
Dim person As New Person
person._name = Name
person._age = Age
Return person
End Function
End Class
End Namespace
(I’m using the XmlnsDefinition assembly attribute here so that I can have a normal-looking Uri in my Xaml, but you can also make references directly to an assembly using the “clr-namespace” syntax, as described here.)
For demonstration, also define a List(Of Person) named “People”:
Namespace PeopleNamespace
Public Class People
Inherits List(Of Person)
End Class
End Namespace
… and then, for example, you can’t load this Xaml, because of the read-only properties:
<People xmlns="https://blogs.msdn.com/mikehillberg/BuilderPattern" >
<Person Name='Mike' Age='5' />
<Person Name='Dwayne' Age='7' />
</People>
(This gives an error of “'Name' property is read-only and cannot be set from markup” ).
But we can create a markup extension that follows the builder pattern to create the object.
First, define the markup extension, named PersonExtension. It looks just like the Person type, except it has setters for the properties, and overrides the ProvideValue function:
<Assembly: XmlnsDefinition("https://blogs.msdn.com/mikehillberg/BuilderPatternBuilder", "PeopleBuilderNamespace")>
Namespace PeopleBuilderNamespace
Public Class PersonExtension
Inherits MarkupExtension
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _age As Integer
Public Property Age() As Integer
Get
Return _age
End Get
Set(ByVal value As Integer)
_age = value
End Set
End Property
Public Overrides Function ProvideValue(ByVal serviceProvider As IServiceProvider) As Object
Return Person.Create(Name, Age)
End Function
End Class
End Namespace
And now we can modify the markup slightly so that it successfully loads:
<People
xmlns="https://blogs.msdn.com/mikehillberg/BuilderPattern"
xmlns:builder="https://blogs.msdn.com/mikehillberg/BuilderPatternBuilder" >
<builder:Person Name='Mike' Age='5' />
<builder:Person Name='Dwayne' Age='7' />
</People>
To test it out, run this code:
Sub Main()
Dim people As People
people = XamlReader.Load(File.Open("..\..\Test.xaml", FileMode.Open))
For Each person As Person In people
Console.WriteLine("Name = " + person.Name + ", Age = " + person.Age.ToString())
Next
End Sub
… and we get this output:
Name = Mike, Age = 5
Name = Dwayne, Age = 7
One last note about markup extensions … Notice that I named the builder class “PersonExtension”, but I referred to it from Xaml as “Person”. That’s possible because when Xaml doesn’t find “Person”, it automatically appends the “Extension” suffix, and looks for “PersonExtension”. If Person and PersonExtension had been in the same Clr namespace, that would have caused confusion, so I put them in different namespaces (“PeopleNamespace” and “PeopleBuilderNamespace”). Alternatively, I could have put them both in the “PeopleNamespace”, and used the specific <PersonExtension> tag (or maybe I would have named it “PersonBuilder”).
Comments
- Anonymous
August 12, 2008
PingBack from http://hubsfunnywallpaper.cn/?p=468