April 16, 2009, 8:15 pm
VB.NET has the sometimes useful feature of late binding, but this seems to lead to poor code. By default, late binding is enabled (ie: Option Strict is set to Off) allowing for implicit narrowing conversions (no cast). Although there are certainly cases where this is a useful feature that can cut down on the amount of reflection code left up to the programmer, I have found that it is more often a cause of less robust code and needless performance degradation.
With Option Strict Off we can write code like this:
Dim obj As Object = "Hello, World!"
Dim str As String = obj
In this case the code will run just fine, and it saved us the hassle of casting obj to String. However, we will obviously run into problems in a situation like this:
Dim obj As Object = "Hello, World!"
Dim int As Integer = obj
Even though int is an Integer this code will compile, but at runtime there will be an InvalidCastException. This is all pretty simple stuff, but the bottom line is that in this case, Option Strict Off gives a runtime error, while Option Strict On gives a compile error. The value of compile-time errors should not be taken lightly, and in my humble opinion they are a programmer’s best friend. With Option Strict On our first sample only needs a minor change:
Dim obj As Object = "Hello, World!"
Dim str As String = DirectCast(obj, String)
Was it really that difficult just to cast it? Type casting is not an inconvenience, but a necessary precaution requiring the programmer to say to the compiler: “Yes, I did intend to perform a narrowing conversion. It was not an accident”.
As a general rule of thumb, I like to set Option Strict On as the project default (go to Project -> Properties -> Compile) and then add Option Strict Off to code files that require it rather than the other way around.
February 28, 2009, 11:45 am
Update: I have posted another article here that explains what I think is a better solution to this problem using a simple PostSharp attribute.
When you raise the PropertyChanged event you have to pass it a property name as a string. If there is no property with that name then nothing will happen. The listener will not be notified and no exception will be thrown making the problem very difficult to debug. You can change this behaviour and make the application fail at runtime by adding a simple check to your helper function for the event:
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
' Throw an exception if the property doesn't exist
If Me.GetType().GetProperty(propertyName) Is Nothing Then
Throw New InvalidPropertyNameException()
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
If you put this in a base class for all of your model views (or controllers, or presenters) then you will automatically get this functionality every time, preventing some potentially very annoying bugs.
This still isn’t the ultimate solution because you don’t find out that the property name doesn’t exist until runtime. Ideally, we would get a compile error when the property does not exist. What I would like to do is call the function like this:
NotifyPropertyChanged(AddressOf MyProperty)
This way you wouldn’t have to use a string at all and the compiler would tell you if MyProperty doesn’t exist. Unfortunately, .NET languages only have delegates for functions/subroutines so there is no way to make a strongly typed pointer to a property. Let’s hope they add that in one day, but until then, we’ll have to use strings.
February 12, 2009, 8:26 pm
By default, validation is not enforced on a binding until after the value has been changed once. Consider this situation: A form has a text box whose contents should not be empty, but its starting value is the empty string. The text box will not show an error when the form is loaded. Instead, it will only show its error style after the user types in some text and then deletes it. Some people would call this a feature, but I prefer to have the validation checked right away so that I immediately know what the required fields are, and which starting values are invalid.
A quick hack
One way to tackle this is to hook a loaded event to the control and then call UpdateSource() from code:
Private Sub myTextBox_Loaded(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs) Handles myTextBox.Loaded
BindingOperations.GetBindingExpression(myTextBox, _
TextBox.TextProperty).UpdateSource()
End Sub
Unfortunately, this really isn’t a desirable solution because it requires some pretty ugly code-behind (not that there’s any other kind) and needs to be done over again for every binding. What we really want is for this functionality to be a part of the binding markup extension.
Creating a wrapper around the binding markup extension
At first I thought it would be easy to just inherit from the Binding class and override ProvideValue, but of course, it is marked as NotOverridable (sealed in C#). Instead, I create a class called ValidationBinding that inherits directly from MarkupExtension and has the new class manage an instance of Binding. The code looks like this:
Imports System.Windows.Markup
Public Class ValidationBinding
Inherits MarkupExtension
Private _binding As New Binding
Private _dependencyObject As DependencyObject
Private _dependencyProperty As DependencyProperty
Public Sub New()
_binding.ValidatesOnDataErrors = True
_binding.ValidatesOnExceptions = True
End Sub
Public Sub New(ByVal path As String)
Me.New()
_binding.Path = New PropertyPath(path)
End Sub
Public Overrides Function ProvideValue _
(ByVal serviceProvider As System.IServiceProvider) As Object
Dim valueTarget = _
DirectCast(serviceProvider.GetService(GetType(IProvideValueTarget)), _
IProvideValueTarget)
_dependencyObject = valueTarget.TargetObject
_dependencyProperty = valueTarget.TargetProperty
If TypeOf _dependencyObject Is FrameworkElement Then
Dim element = DirectCast(_dependencyObject, FrameworkElement)
If element.IsLoaded Then
ForceValidation()
Else
AddHandler element.Loaded, AddressOf ElementLoaded
End If
Else
ForceValidation()
End If
Return _binding.ProvideValue(serviceProvider)
End Function
Private Sub ForceValidation()
BindingOperations.GetBindingExpression(_dependencyObject, _
_dependencyProperty).UpdateSource()
End Sub
Private Sub ElementLoaded(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
ForceValidation()
End Sub
Public Property Path() As PropertyPath
Get
Return _binding.Path
End Get
Set(ByVal value As PropertyPath)
_binding.Path = value
End Set
End Property
... the rest of the binding properties go here
End Class
The binding can then be used like this (where my is an imported namespace containing ValidationBinding):
<TextBox Margin="5" Text="{my:ValidationBinding Path=Text}" />
As an example, I have exposed the Binding’s Path property, but you actually have to do this for all of the public properties in Binding. A reference of all the properties that should be implemented can be found here.
ProvideValue returns the result of the Binding’s ProvideValue function, but first it checks whether the binding target has finished loading. If it has already finished loading then it forces validation by calling UpdateSource() on the target. In the much more likely scenario that the control has not yet loaded (this will be the case when you set your binding in XAML) it attaches a handler to the Loaded event so that the ForceValidation subroutine can be deferred until it is finished loading.
Also, notice that I set both ValidatesOnDataErrors and ValidatesOnExceptions to True in the constructor so that I didn’t need to specify it in the XAML. Chances are that whenever you use this markup extension you will want those enabled anyways.
It may seem like a lot of work, but it is a very reusable solution that gives you significantly more power.
February 9, 2009, 8:50 pm
The LINQ to SQL DataContext provides excellent functionality for managing a set of local changes to a database that can be pushed to the server with a single call to SubmitChanges(). Inevitably there will be situations where you want to discard the changes you have made, effectively allowing you to continue using the DataContext as though those changes had never been made. Unfortunately, there is no DataContext.DiscardChanges() method.
A little research reveals that this is by design and that you should simply recreate the DataContext in these cases, but of course, nothing is that simple. Every object you have that came from the original DataContext now needs to be reset to use the new one to guarantee predictable behaviour. For example, the last line of this snippet will set objectsAreEqual to False:
' Create two data contexts
Dim dc1 As New UsersDataContext
Dim dc2 As New UsersDataContext
' Grab the same record out of both the data contexts
Dim user1 = dc1.Users.First
Dim user2 = dc2.Users.First
' False, because the otherwise identical records come from different contexts
Dim objectsAreEqual = user1 = user2
Basically, this shows that it does matter where your object came from. If you just want to discard a few little changes and you don’t want to have to recreate all your variables, you can instead undo all the changes that you have made so far and continue to use the same DataContext.
Undoing inserts and deletes is easy, because we can just do the opposite to revert the change. Luckily there is even a nice function on the DataContext that will show us what we need to do called GetChangeSet:
Public Sub DiscardInsertsAndDeletes(ByVal data As DataContext)
' Get the changes
Dim changes = data.GetChangeSet()
' Delete the insertions
For Each insertion In changes.Inserts
data.GetTable(insertion.GetType).DeleteOnSubmit(insertion)
Next
' Insert the deletions
For Each deletion In changes.Deletes
data.GetTable(deletion.GetType).InsertOnSubmit(deletion)
Next
End Sub
LINQ to SQL is smart enough to know that if a row is inserted and then deleted (or the other way around) nothing needs to be done. If you look at the ChangeSet after running this function, you will notice that the Inserts and Deletes collections are empty. Updates are a little more annoying. If you update a value, and then set it back to its original state then you will get the expected behaviour (ie: the ChangeSet will be empty because you reverted your change) but unless you write code to keep track of the original value yourself, there is no way to automatically put an object back into its start state without hitting the database. If hitting the database is acceptable, you can always use the Refresh function to get rid of the updates:
Public Sub DiscardUpdates(ByVal data As DataContext)
' Get the changes
Dim changes = data.GetChangeSet()
' Refresh the tables with updates
Dim updatedTables As New List(Of ITable)
For Each update In changes.Updates
Dim tbl = data.GetTable(update.GetType)
' Make sure not to refresh the same table twice
If updatedTables.Contains(tbl) Then
Continue For
Else
updatedTables.Add(tbl)
data.Refresh(RefreshMode.OverwriteCurrentValues, tbl)
End If
Next
End Sub