Back to Basics - This is not the object you're looking...wait, oh, it is the object
Downcasting is kind of something you usually want to avoid, but sometimes it's not easily avoided. It depends on the situation. Because it's not an idiom you'll find yourself doing every day, sometimes you'll forget to do it entirely and what you're looking for is right there under your nose.
The Problem
A buddy was trying to host the WebBrowser control in his WinForms application. It was a locally run application that was using the WebBrowser control to generate part of the UI. The user might click on a link, and you'd want to do something based on the click of an link/anchor in the HTML.
In this trivial example, I add the words "Add Comment" to a page I've navigated to within my WinForms Application.
First, we make some new elements within the WebBrowser control itself, like this.
HtmlElement div = webBrowser1.Document.CreateElement("div");
div.SetAttribute("style", "color:blue;");
webBrowser1.Document.Body.AppendChild(div);
HtmlElement anchor = webBrowser1.Document.CreateElement("a");
anchor.InnerText = "Add Comment";
anchor.Id = "lnkAddComment";
anchor.SetAttribute("href", "#");
anchor.Click += new HtmlElementEventHandler(doit);
div.AppendChild(anchor);
Notice that we can hook up a managed event handler to the anchor, and that the anchor's href attribute points to nothing, by setting it to "#".
anchor.Click += new HtmlElementEventHandler(doit);
My friend hooked up the event to doit() that had a standard EventHandler signature like this:
public void doit(object sender, HtmlElementEventArgs e)
You've likely seen this "object sender, SomeEventArgs e" method signature before. He started digging around in the HtmlElementEventArgs object, trying to find something he could use to known if the user had clicked on the link called lnkAddComment. He was bummed to find nothing he could use.
There's a couple of thing to learn here. First, just hovering over the sender object in the debugger shows us a lot. We can see that the type of sender isn't just object, but rather System.Windows.Forms.HtmlElement. This is a CLR type, and not a JavaScript type or a type you'd know about if you were familiar with the DOM and were expecting something more DOM-like.
The DOM element in this case, is there, but it's a COM Object and is part of the IE DOM object model and you'd be better off using Visual Basic if you care deeply about getting into it as we've recently learned.
Downcast, Damned Spot!
However, it's an HtmlElement and it has and id property with the string "lnkAddComment," and that's useful. At this point one could down-cast it. That means, telling the compiler that we know more than it does and that we're totally sure and are willing to risk our necks.
HtmlElement foo = (HtmlElement)sender;
if (foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}
There's some interesting things we can think about here. First, what if we're wrong? Well, bad things for one:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Unable to cast object of type 'Something.You.Did.Not.Plan.On' to type 'System.Windows.Forms.HtmlElement'.
at WindowsFormsApplication1.Form1.doit(Object sender, HtmlElementEventArgs e) in C:\Yay\Form1.cs:line 32
And that sucks. However, in this case we are pretty darn sure, but we should be more defensive about it.
public void doit(object sender, HtmlElementEventArgs e)
{
if (sender is HtmlElement)
{
HtmlElement foo = (HtmlElement)sender;
if (foo != null && foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}
}
}
But that is kind of verbose, we can try a defensive cast:
HtmlElement foo = sender as HtmlElement;
if (foo != null && foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}
In this case, if sender isn't an HtmlElement, we'll get back null. The same code in VB is pretty clear also. See the TryCast?
Public Sub doit(ByVal sender As Object, ByVal e As HtmlElementEventArgs)
Dim foo As HtmlElement = TryCast(sender,HtmlElement)
If ((Not foo Is Nothing) AndAlso (foo.Id = "lnkAddComment")) Then
MessageBox.Show("woot")
End If
End Sub
The IL (Intermediate Language) instructions are interesting also as it's asking explicitly if that object is what I think it is:
L_0001: ldarg.1
L_0002: isinst [System.Windows.Forms]System.Windows.Forms.HtmlElement
I suppose that means I could just ask myself, but that's largely stylistic as the resulting IL is virtually the same, with the defensive cast being a simpler.
But Why the Downcast at All?
At this point you might be asking, Why did I have to downcast at all? Doesn't that mean the EventHandler pattern is lame? Maybe, but here's the thinking as described by Jeffrey Richter on page 228 of his most excellent CLR via C#, 2nd Edition (with [contextual edits and emphasis] by me so it makes sense in this post).
A lot of people wonder why the event pattern requires the sender parameter to always be of type Object. After all, since the [HtmlElement] will be the only type raising an event with a [HtmlElementEventArgs] object, it makes more sense for the callback method to be prototyped like this:
void MethodName(HtmlElement sender, HtmlElementEventArgs e);
The pattern requires the sender parameter to be of type Object mostly because of inheritance. What if [HtmlElement] were used as a base class for [DerivedHtmlElemen]? In this case, the calIback method should have the sender parameter prototyped as an [DerivedHtmlElement] instead of HtmlElement, but this can't happen because [DerivedHtmlElement] just inherited the Click event. So the code that was expecting an [DerivedHtmlElement] to raise the event must still have to cast the sender argument to an [DerivedHtmlElement]. In other words, the cast is still required, so the sender parameter might as well be typed as Object.
The next reason for typing the sender parameter as Object is just flexibility. It allows the delegate to be used by multiple types that offer an event that passes a [HtmlElementEventArgs] object. For example, a [PopHtmlElement] class could use the delegate even if this class were not derived from [HtmlElement].
One more thing: the event pattern also requires that the delegate definition and the callback method name the EventArgs-derived parameter e. The only reason for this is to add additional consistency to the pattern, making it easier for developers to learn and implement the pattern. Tools that spit out source code (such as Microsoft Visual Studio) also know to call the parameter e.
If you're interested in this kind of stuff, you should totally buy his book.
Can we avoid the cast completely?
Why did we downcast in the first place? We wanted to get ahold of the HtmlElement.Id property so we could do a string comparison in order to tell if an object is the one we're looking for. Perhaps we can check for that object's identity using something a little cleaner than a string.
In this case, since we were the ones that created the anchor in the first place we can check the "object sender" against a saved reference to our anchor by checking object identity. Are these the same two objects? Is the object I added to the object model the same one that is coming back to me as the sender parameter to this Event Handler?
HtmlElement anchor;
public void doit(object sender, HtmlElementEventArgs e)
{
if (sender.Equals(anchor))
{
MessageBox.Show("woot");
}
}
Is this a good idea? What about using == instead? In this case, I CAN use == because HtmlElement has explicitly created equality operators, so when I'm using == to compare these two instances I'm ACTUALLY calling a static op_Equality(HtmlElement, HtmlElement) on the HtmlElement type. It's static because both side might be null and I can't call methods on null instances.
HtmlElement anchor;
public void doit(object sender, HtmlElementEventArgs e)
{
if (anchor == sender)
{
MessageBox.Show("woot");
}
}
However, while operator overloading is common in C++ it's generally considered to be unnecessarily obscure in C# and moreover, you just can't count on folks to be consistent. For example, when I say == do I mean value equality or reference equality? In the case of HtmlElement they mean reference equality. In C++ an overloaded == is usually done for deep value comparisons.
I will get this warning if I try anyway:
Possible unintended reference comparison; to get a value comparison, cast the right hand side to type 'System.Windows.Forms.HtmlElement'
I like to use Equals() myself for clarity's sake, but touché! It's going to just do this internally anyway:
public override bool Equals(object obj)
{
return (this == (obj as HtmlElement));
}
Commenter Adam makes the excellent point that using Object.ReferenceEquals is explicitly clear in expressing intent. The result would be:
public void doit(object sender, HtmlElementEventArgs e)
{
if (Object.ReferenceEquals(anchor,sender))
{
MessageBox.Show("woot");
}
}
Of course, the implementation of ReferenceEquals is just this again. ;)
public static bool ReferenceEquals(object objA, object objB)
{
return (objA == objB);
}
What's the point?
Know your options, but above all, know your intentions and make sure that the code you're writing correctly expresses your intent.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.



About Newsletter