Things I wish were better…


A while back, I wrote the following in a post:


I continue to be satisfied about the design of C#, but even in our second version, there are a couple of cases in the past few months where we’ve said, “Well, if we’d been thinking about that when we first did the design, that would be a wonderful way to express that concept, but since we didn’t, we’re stuck with what we have now.”


There are two cases that come to mind that I think I’ll talk about.


The first – and I’m not positive that it qualifies based on what I said above, but I’d like to talk about it anyway – is the fact that we have both the .Equals() method and operator==() in the language. This situation gets complicated by the fact that there is both reference equality and value equality (sometimes for the same object, such as string), and it’s hard to come up with a simple scheme that does both. Having said that, this is an area that I don’t think we excelled in.


The second is a somewhat arcane consideration that isn’t especially language-related, but provides a good example of the constraints that timing has.


The Type class (ie what you get when you use typeof(x) or y.GetType()) has a number of members that describe the type. For example, you can find out whether the type is an array, whether it’s a pointer type, etc. In the underlying metadata representation, these attributes are typically encoded into specific bits in the metadata for the type. You can think of this as our primary metadata system, and it was one of the first parts of .NET that was built.


Somewhat later, we created a second metadata system known as Custom Attributes. Rather than being stored as specific bits, custom attributes are stored as blobs of data hung off a piece of metadata. So, it’s puzzle time:


What is the metadata representation of the following:


[Serializable]
class Test
{
    ….
}


This is an example of what is know as a “pseduo-custom“ attribute. Serializable looks like an attribute, but instead of being stored as an attribute, it’s stored as a metadata bit, and is accessed through the IsSerializable property on the type object. Which means that if you do typeof(Test).GetCustomAttributes(), you won’t get back a serializable attribute. That can lead to some confusion, and in some cases, the reason we’re in that state is because we didn’t have a full-fledged implementation of attributes early (I’m not sure whether Serializable is one of those cases, but it does demonstrate the issue).


There are two implementations of pseudo-custom attributes. In the first one, the compiler has special-case code for the attribute, and emits the relevant metadata bits. In the second one, the compiler treats it like any other attribute, and then the runtime removes the attribute at sets the metadata bit. The C# compiler uses both approaches (or at least it did at one time).

Comments (9)

  1. jaybaz [MS] says:

    I would have made typeof() use member syntax, instead of free function syntax.

    I would still do it today, and break existing C# apps. If we believe in C# for the long term, then the number of uses today is a small fraction of the total number that will ever exist.

  2. Roger Heim says:

    I tend to use the == syntax for simple equality comparisons. Things like "if (some_int_value == 2)" or "if (some_decimal_value = 499.99)".

    I tend to use the .Equals syntax when comparing objects, for example, if I have four RadioCheck menu options who’s click events point to the same event handler. I’ll write "if (sender.Equals(miFirstOption))".

    Just my personal preference.

  3. Robert Kozak says:

    I got bit by this a few weeks ago. I was trying to find the Serializable attribute on my class.

    took me a few hours to figure out what was going on. I had forgotten that serializable is not really an attribute. Even though it look like one.

    Robert

    p.s. I dont like the term psuedo-custom attributes. I prefer intrinsic attributes. What do you think?

  4. Darren Oakey says:

    With the serializable thing – there’s an easy change to make to sort of edge the old usage out. Theres no reason that GetCustomAttributes and such functions can’t just pretend to return a serializable attribute if defined… Just because the attribute actually happens to be stored in the metadata, doesn’t mean it has to be reflected as such. I can’t see that it would break any existing code, and people in the future could just use it as they use any other attribute.

  5. I kind of like the equality / equivalence seperation. I think the problem is that you chose "Object.Equals", rather than "Object.IsEquivalentTo."

    You could always add "Object.IsEquivalentTo" as an alias supported by the compiler, and have the compiler flag references to "Object.Equals" as depricated stating something like "warning: Object.IsEquivalentTo is generally prefered to Object.Equals as its meaning is more clear". That would seem reasonable to me.

    Couldn’t you also generate a dummy attribute type for each of the built in "attributes" and embed them as custom attributes? If compatibility is an issue, then you could add a method to Type called GetCustomAttributesEx that "injected" attributes for the built in bits into the returned array. If you don’t want to have to make copies of attribute instances then you could have GetCustomAttributesEx return an ICollection.

  6. Jean-Claude Manoli says:

    NDoc is able to get the [Serializable] attribute without calling IsSerializable, so I guess it’s not one of the pseudo-custom attributes mentioned in the post. Does someone know an example of a real pseudo-custom attribute? Is there a list somewhere (so I can handle them in NDoc)?

    Speaking of attributes, does someone know why the attribute [StructLayout(CharSet="Ansi", Value="Auto")] gets added to all classes since .net v1.1?

  7. I think that there is no need for ==

    for strings, since we have Equals. Also it is obvious that == is implemented through Equals inside of it.

  8. Kjell Holmgren says:

    The philosophical issue of identity versus equivalence is something that has caused me a lot of anguish, and has kept me up at night. I saw that as a sign that I am indeed a geek. Glad to hear that I am not alone. 🙂

    It is probably not feasible to prohibit the use of non-readonly fields in GetHashCode() and Equals(), but it might be a good idea to put a strong recommendation/discussion about that in the documentation…