Monday, August 07, 2006

The Invisible Member

When writing objects that implement or inherit interfaces, it is a rule that you must implement the interface in full. You cannot remove members from the interface or just not provide an implementation for them. Otherwise, someone using your component through the parent/implemented interface may run into an unexpected error because your component has broken the contract it agreed to by implementing that interface.

So what do you do when you've inherited a member that isn't useful in the context of your derived class ? Using C# and .NET you can actually hide the member to avoid confusing developers consuming your component. There are two things to note here;

1. When we talk about 'hiding' the member, we're not talking about hiding it by replacing it with a new implementation, which is the common usage for the term hiding. 'Hiding' a member this way involves using the new operator, where as hiding a member to deter other developers from using it (like we're talking about) uses an attribute.

2. The member is only 'hidden' in the sense that it doesn't appear in intellisense, or the object browser, property window etc. The member can still be used, and it can be found via reflection etc. If someone calls the method or sets/requests the property value, the call will still succeed because the member really does exist. However, since no one knows the property exists unless they really go looking for it, it should only be called if the developer is using the parent object's interface.

As an example, let's look at the TableLayoutPanel control. The TableLayoutPanel inherits from the Control class, which has a Text property, and therefore the TableLayoutPanel must also have one. If you place a TableLayoutPanel control on a form and view it's properties in the property window, you won't see a Text property. If you go to the code view and type

tableLayoutPanel1.Text = "my new value";

you'll find the intellisense doesn't list the Text property, but the code will compile and run. How can this be ? It's because the Text property is hidden using the System.ComponentModel.EditorBrowsable attribute. You can apply this attribute to a class member, like so;

[System.ComponentModel.EditorBrowsable(EditorBrowsableState.Always)]
public string Text
{
get { return text; }
set { text = value; }
}

There are three values for the EditorBrowsableState enum; Always, Never and Advanced.
Always and never are fairly self-explanatory, however, Advanced might require a little more explanation. In the Visual Studio options dialog (tools -> options), under Text Editor\All Languages you'll find a checkbox labeled Hide Advanced Members. If you check this box, you'll see all members whose attribute is set to Always or Advanced (or where the attribute hasn't been applied). If you uncheck this field, you won't see members with the attribute specifying Advanced.

Personally, I always check this box. That way, if I'm using intellisense to look for a member to perform some action or if I get someone else's code from the Internet as an example, I'm not confused by members I can't see.

For members that don't apply to your component you can usually leave/override the member with a blank implementation, or leave the member with the base class' behaviour. If you really don't want people calling a method for some reason you can throw the NotImplementedException from the method or property accessors. Note this exception doesn't exist in the compact framework, and as a consequence Visual Studio sometimes (incorrectly in my opinion) writes code for you that throws new Exception (which isn't recommended by most people, or even by FXCop). You should always change this code to throw the NotImplementedException so code consuming your component can catch this exception explicitly and handle the result gracefully.

More importantly, you need to be very careful where you throw this exception. If you're component is being consumed by a framework, the framework may or may not support catching the exception and failing gracefully which could mean your component crashes the application. If you're component is stand-alone, you should still clearly document the fact the member throws this exception, and you should consider as many foreseeable contexts for your component as possible to make sure the exception never causes a problem it shouldn't.

2 comments:

  1. Hi,this is Arun from INDIA.I just need to clarify about TableLayoutPanel..NET Framework 2.0 has it.My question is "Can we make this control available in Compact Framework?" or can it work in Win CE.I would really be glad if you help me out

    Thanks in Advance

    ReplyDelete
  2. Hi,

    I'm afraid the TableLayoutPanel and FlowLayoutPanel controls are NOT available in the compact framework.

    I was going to suggest trying to build your own, but it appears the CF version of a form/control doesn't support the layout event either, so you won't be able to use tutorials on the 'Net for building other styles of layout control as a base, unless they use manual methods (such as coding everything in the resize event/onresize method override).

    In any case, this is likely to be a lot of work, to get the control to work right both in the designer and at run time.

    Your best bet is probably to find a third party control.

    Sorry I couldn't be more help.

    ReplyDelete