One of the things I love about my job is that I get to make people happy. How do I do that? By giving them what they want of course! One of the things I don’t like so much about my job is going back on a decision that we made before, and having to revert some of the behavior. Well, we’ve been talking about COM interop in C# 4.0, and are now in the thick of things. Today we’ll chat about a feature which many people have asked for in the past, but we’ve stayed away from until now – Indexed Properties.
What are indexed properties?
Great question! Indexed properties are exactly what they sound like – they’re properties that you can index into. In a sense, you can think of them as an atomic form of accessing a property, and then indexing off of the return value of the property. The following is an example of an indexed property access:
Indexed properties under the covers are simply accessor methods and a property, much like regular properties, except that both the accessor methods and the property have extra parameters. Or another way to look at them is that they’re just regular indexers except with a name other than “Item”.
Why didn’t we add them earlier?
We didn’t add these guys earlier because we felt that this wasn’t the design principle that we wanted people to conform to. We believed (and still do!) that the correct meaning of our sample statement is “I’m accessing a property named P off of my type C. Once I’ve got a resulting value, I’m going to index off of it.”
So what’s the issue here?
The issue is that while we firmly believe that the current C# semantics are the right ones, we unfortunately have a large legacy set of libraries that have shipped with indexed properties – COM.
We’ve applied the same philosophy to indexed properties that we discussed last time, and have put support in the language for them only in COM interop scenarios.
On the declaration front, we have not provided the ability to declare these guys. Since true COM libraries are created via tlbimp, these should never be created in C#.
On the consumption front, the compiler will now import anything that looks like an incdexed property off of a COM type (ie a type with the tdImport flag), and allow it to be used via the named indexer syntax. Keen readers will note that the above sentence reads “the compiler will allow the indexed property to be consumed via named indexer syntax”, and not “the compiler will treat the indexed property as a property”. The difference being, of course, that true properties cannot be accessed via their accessor methods directly, but in order to maintain backwards compatibility, accessing the accessor of an indexed property directly must still be allowed.
Let me go into a bit more depth and using our sample statement again describe the changes that we’ve made to overload resolution to allow these guys to be bound.
The first thing the compiler does when it sees the statement is it binds the left side of the dot, and resolves myObject to its type, say MyType. Once it’s done that, it goes and looks up everything with the name MyIndexedProperty on MyType.
In order to maintain backwards compatibility, if the compiler finds something other than an indexed property, it will go ahead and bind to whatever it found. Indexed properties in that sense are like extension methods – they only apply when all the normal binding steps have failed. Notice however, that this happens on name lookup time, and not on the indexer argument resolution time. More explicitly, if the compiler found a regular property named MyIndexedProperty of type C, and C does not have an indexer on it, the compiler will not bind to an indexed property named MyIndexedProperty. It will provide the same error you would have gotten with the C# 3.0 compiler – namely that there is not an indexer on the type C.
Once the compiler has verified that “normal” binding (ie binding that we would have done without considering indexed properties) has failed, it then checks the type MyType to see if it’s a ComImport type. If it is, then the compiler looks at all available indexed properties and performs the regular overload resolution algorithm on the indexed property candidates much like it would for regular indexers. It simply treats the candidates as indexers with names.
Because we bind the indexed property like named indexers, we generate IL as if we were calling an indexer that has a name – namely, we call the get_Index method, but with the name of the indexed property.
In our example, the IL we generate would simply be a call to myObject.get_MyIndexedProperty(index), which is exactly what users had to write in previous versions of the language. Because indexed properties (and indexers in general) are really just syntactic sugar for calling accessor methods, nothing special needs to happen here both from a compiler standpoint and a runtime standpoint.
So really, these guys are just sugar?
Yep. These guys are just nice syntactic sugar. The great thing about sugar though, is that it really is sweet. Let me just leave you with a comparison of a common scenario.
// C# 3.0 myObject.set_MyIndexedProperty(myObject.get_MyIndexedProperty(index) + 1); // C# 4.0 myObject.MyIndexedProperty[index]++;
The indexed property syntax is much nicer isn’t it? If you’re a COM programmer, I’m absolutely positive that you’ll agree!