Partager via


Properties Part 2 - defining default properties

Disclaimer. This is an ancient post. By the looks of it, I originally intended to write this almost a year ago, as a follow up to my scalar properties writeup. That was back when I was testing properties (and more exactly, default properties) and some of the design was still in flux. Now it's pretty nailed down (there's very little time to change it for Whidbey, anyhow), and it dawned on me that I hadn't posted this material. So, I'll do it now, with a bit of fixup.

The Way C# Does It. Reader Rob Walker asked over a year ago:

Why do I have to specify the type of the property 3 times in the definition? It makes this new syntax more verbose than the old.  Why not just adopt the C# style?

Let me start by mentioning that I'm not a member of the design team. I've been included on various discussions that take place regarding the syntax, but I haven't been the one making the decisions. I can only make guesses as to their reasonings (or ask them). Either way, the reasoning is my own.

I believe there are two reasons why we wouldn't adopt the C# style. First, there's an existing property syntax that users are familiar with. The changes between the two syntaxes are somewhat minor. Second, the C# property syntax doesn't fit well with C++ paradigms. Though simple, I imagine the syntax could wreak havok on parsers, and could introduce a number of ambiguities in the language. One of the goals of adding CLI to C++ was to not break existing paradigms, where possible. Finally, the designers wanted to do what felt natural to C++ users. C++ users aren't familiar with functions that have no parameter lists, that's unusual.

Speaking of how C# does it, here it is:

public class ArrayWrap {
public ArrayWrap(){ arr = new int[10]; }

  public int this[int idx] {
get{ return arr[idx]; }
set{ arr[idx] = value; }
}

  private int[] arr;
}

And here's code to produce the equivalent class in C++:

public ref class ArrayWrap {
public:
ArrayWrap(){ arr = gcnew array<int>(10); }

  property int default[int] {
int get(int idx){ return arr[idx]; }
void set(int idx, int value){ arr[idx] = value; }
}

private:
array<int>^ arr;
};

We stole the default keyword and co-opted it for use in indexed properties. Note that you still make use of the property keyword. When this comes out in IL, it looks almost exactly like the C# indexer - with the default property being named "Item" and the getters and setters also thusly named. You can change the name of the default property by using the attribute System::Reflection::DefaultMember on the class. The compiler sees this attribute and interprets it to mean that the default member you created in the class should be named whatever you pass in the attribute ctor.

A weird IL trick leads to another way you can generate a class with a default indexer using that attribute. In IL, a property is a default indexer if it meets two requirements: 1) it is an indexed property, and 2) its name matches the name in the DefaultMember attribute. So, you can actually generate a default member in C++ using code like this:

[System::Reflection::DefaultMember("Foo")]
public ref class ArrayWrap {
public:
property int Foo [int] { ... }

However, I do not recommend this approach, except as a possible way to workaround bugs, or to obfuscate for fun, as the C++ compiler will not recognize Foo as a default property when compiling, only when importing. Also, although it is legal in IL, I do not recommend using this trick to make default scalar properties. That's just unpleasant.

In a future posting, I'll go over the various ways you can access properties, which may come in handy as workarounds.