Things That Make You Go Hmmm

Things That Make You Go Hmmm

Rate This
  • Comments 60

As you might have gathered from the number of times I make a blog post beginning with "I got the following question from a reader the other day..." I field a lot of questions about the C# language (and in the past fielded a lot of questions about VBScript and JScript.) Each of them is, by definition, about something that was unobvious about the language; if it were obvious, the question wouldn't have been asked in the first place.

This means that I always learn something when I answer questions. I learn what it is about this language that people find confusing, unobvious, tricky. These are useful data, because we can then use them to make better compiler warnings. We can use them to help us gauge early on what new language features people will find confusing. And so on.

Rather than wait for more questions to come in -- though believe me, plenty do -- I thought I'd take this opportunity to solicit stories of times when you learned something profoundly non-intuitive about programming languages. Preferably C#, but other languages would be interesting as well. If you've got a story to share, please leave a comment on the blog or send me an email. Thanks!

  • Hi, Eric.

    My story about non-intuitivity and  how I learned about variance/contravariance when trying to use (or abuse?) C# 2.0 generics...

    I defined a TimeConverter<TTime> class where TTime was the ancestor of TDecade, TYear, TQuarter, TMonth and so on.  The idea was to manage all kind of conversions between the diffrent descendants (i.e. days to quarters, decades to months, etc.)

    But later my surprise was big -and also my disappointment- when discovered that things weren't so easy.

    I came to your blog precisely investigating about the issue. Fortunately I managed to workaround the problems and things now are clear after reading these long and informative series of the past years.

    Greetings.

  • class Derived : Base

    {

       public Derived(SomeType x)  : base(x)

       { ... }

    }

    Took a little while for this syntax to become natural, I'm not sure why.  I kept looking for ways to execute some code in the Derived constructors body where at some point I would expect to call base(x) as if I was overriding a method.  This despite accepting that a constructor on the base class must be called.

    Took me ages to learn that the base constructor must run before any in the derived and I still wish I could at least do some munging on the parameters before letting the base class have them for its constructor.

  • You can mess around with the parameters to the base ctor:

    public Derived(int x) : base(x + 1) {}

    that's perfectly legal.

  • My non-obvious story isn't as clear-cut because I don't recall the exact circumstances (this was in 2002), but it has to do with static methods/properties (I'll use "static method" throughout to refer to both). Basically, the first time I tried to put a static method in an interface, it failed, and the first time I tried to override a static method in a derived class, it failed.

    You had a post recently on why this is - it's because static methods merely are methods for which lookup can be made at compile-time, not class methods. This made me go not just "hmmm" but also something more vivid and unprintable than "hmmm", simply because of the two, I'd rather C# supported class methods. Class methods are very useful tools when it comes to really working with the hierarchy that object orientation in C# gives you; imagine inheriting from an abstract bitmap class and in the process overriding a class property, providing an easily way of checking, for example, what type or extension or color depth is supported by a specific image format. Sometimes class methods can be supplanted by attributes, but far from always -- it doesn't solve any of my two previously mentioned gotchas.

    In my seventh year of using C#, it still boggles my mind. I can see the case for static methods as far as executional expediency goes (no virtual dispatch), but I literally cannot see any other advantages over class methods. I recognize that class methods overtaking static methods at this point would be a compatibility nightmare and that I am likely out of luck, but if you entertain the question as if it was written and considered before C# 1 was ever public, what would your thoughts be on the subject?

  • @Eric,

    Thanks that makes the point well.

    on its own:-

    public Derived(int x) : base(x) {}

    It didn't occur to me that I could do:-

    public Derived(int x) : base(fn(x)) {}

    I've coding in C# for some years and yet I still struggle to grok this.  It could just be me being on the slow side but it just doesn't seem that obvious.

  • @Anthony: I think the only reason C# has that syntax is because otherwise they'd have to invent a way of calling the initialization part of the constructor (what runs in the constructor and not the creation of the object itself) in regular code. Calling "new" creates the new object and then runs the appropriate constructor. It's not obvious, but it's also a special case, because you normally don't want to separate the two.

  • I was surprised I could not constrain generic types with parameterized constructors.

    After much thought and discussion, I understand why that is the case (generic types describe an interface; construction is an implementation detail).

    Good call on that.

  • I recall being very surprised when I've seen that arrays are covariant in .NET, in any position (I didn't come from a Java background, so I didn't realize where that misfeature was coming from, as it's certainly rather counter to common sense).

  • Not exactly a "hmm...", but I've wondered why languages (especially a new one like C#) don't go the "extra mile" to guide you (if not practically enforce) accepted "best practices" (and yes, I understand that such guidance could change over time).

    As a specific example: it's generally agreed that inheritance is overused; given that, why not make the default "sealed" or at least allow "base" to be explicitly used.  Similarly: it's a lot easier to just inherit from List<Foo> (wrong) than to implement IFoo<List> using List<Foo> (right).

    In somewhat of a similar vein, the D language doesn’t have compiler warnings; the code is either right or wrong.

  • When I was first learning C, it took me a while to get to grips with pointers. The concept of a memory location that held the address of another memory location made perfect sense to me but the syntax confused me. In C, pointers are usually declared something like "int *x;" suggesting that you're creating a variable whose type is "int" and whose name is "*x". That's sort of what you're doing except not really at all - at this point you haven't created any variable of type "int". The C++ way of declaring pointers, "int* x;" (suggesting you're creating a variable of type "int*" whose name is "x") makes much more sense.

  • Several years ago, after I'd been a VB programmer for a long time and had already moved to C#, I was surprised to see code using Mid() on the LHS, instead of the RHS. I had no idea a string could be modified in this way.

    [[ ERIC: We deliberately left that out of VBScript. It is deeply weird. ]]

  • I was surprised when I first saw a singleton which looked okay but wasn't as lazy as I expected. At first I didn't believe it (this was a newsgroup question, btw - where I've learned most of the crazy stuff) but when I tried it, sure enough it failed. These are the two different code snippets:

    public class LazySingleton

    {

       public static final LazySingleton Instance = new LazySingleton();

       static {}

       private LazySingleton() {}    

    }

    public class NonLazySingleton

    {

       public static final NonLazySingleton Instance = new NonLazySingleton();

       private NonLazySingleton() {}    

    }

    The only difference is an empty static constructor. As so often happens, enlightenment came from a mixture of careful spec reading and checking stuff with Reflector.

  • The fact that using() with object initializers can cause a memory leak is certainly counter-intuitive.

  • Re: best practices: I would say that C# does do a fairly good job of leading you towards the Pit of Success rather than the Pit of Despair, but of course we can always do better.

    Re: sealed: Yes, as I have stated in this space before, I would have made "sealed" the default.

  • The yield statement always throws me for a loop.

    Did you intend for the title of the article to be a C+C Music Factory reference?  If so, that's just bleeding awesome.  If not...

Page 1 of 4 (60 items) 1234