C# in Depth

Cover of C# in Depth
Order now (3rd edition)

(You are currently looking at the first edition version of this page. This page is also available for the second and third editions.)

Notes for Chapter 2: Core foundations: building on C# 1

2.2.1: Strong vs weak typing

Most of the definitions given for strong and weak typing involve details of what information is available at compile-time, what information is available at execution-time etc. However, Eric pointed out that a lot of the time the definitions really boil down to:

At that level, the terms become relatively meaningless.

2.2.1: Terminology for what is currently "static"

In the book, I don't go into very much detail about what static really means, but Eric wrote a blog entry on static methods containing this:

Static methods are called "static" because it can always be determined exactly, at compile time, what method will be called. That is, the method can be resolved solely by static analysis of the code.

Apparently this caused a bit of a ruckus - the word "static" isn't terribly well chosen. It does mean what it says, but that's not how people actually think of it. Do you think, "Hmm... I want a method which the compiler can resolve with only static analysis" or do you think, "Hmm... I want a method which relates to the type itself rather than any specific instance of the type"? I know I do the latter.

So, what are the other options? VB uses "Shared" which is closer in some ways - but still misses the boat in my view. Sharing involves something being used by more than one person (or instance in this case). Static members aren't shared between instances - they're present even if there are no instances at all!

Other options (in a totally imaginary language) might be "typewide" or "noninstance". Neither of these appeal, to be honest. Do you have any better ideas?

2.2.1: Static, dynamic and middle grounds

There is a middle ground between "totally static" and "totally dynamic". There's "mostly static, but dynamic where necessary" - which is the route VB has chosen. It's also a route C# might take in the future.

2.2.1: More benefits on static typing

Joe Albahari mentions these additional benefits of static typing:

  • Refactoring a large program is dramatically easier and safer with static typing. For instance, if you change a parameter’s type on an internal or private method, all you need do is rebuild, and the compiler will tell you everywhere that needs updating. With a duck-typed language, you have to instead rely on unit tests which invariably don’t give you 100% coverage (especially with UI code) and so you end up with residual bugs. I programmed for 5 years in a duck-typed object-oriented language, and remember that bugs from refactoring would sometimes show up months later!
  • The IDE can perform certain kinds of refactoring automatically, such as renaming a type or member. Having good identifier names, I think, is essential for long-term maintainability. In a duck-typed language, you almost never dare rename anything, once the project reaches a certain size, for fear of introducing bugs.
  • IntelliSense (as you point out) is a showstopper. Ask most people whether they’d be willing to rescind autocompletion for, well, almost anything, and they’d say no.
  • 2.3.3: When is it appropriate to write your own struct?

    In section 2.3.3 I talk a bit about the differences between structs and classes, but I don't give much guidance on when to use what.

    In my experience, the "default correct choice" is to write classes. When it comes to immutable types, you could argue there's not a huge amount of difference between using a class and using a struct, beyond natural nullability and memory behaviour - but there's a difference in gut feeling.

    By and large, I only write custom structs to represent some sort of basic quantity. For instance, I can imagine writing a Money struct which encapsulated the amount (as a decimal) and the currency (possibly an enum, or more likely a reference type with a small number of shared instances). I'd be very unlikely to encapsulate a person as a struct, or a collection.

    There's more on this topic in the Microsoft design guidelines.

    One important rule to avoid breaking: structs should almost always be immutable. Mutability in structs can cause horrific bugs which are really hard to track down. It's even worse if the mutability is available through an interface the struct implements. Just say no.

    2.3.4: Boxing copies values

    One point I didn't mention when discussing boxing was that the process of boxing always copies a value. The newly created box doesn't know about the variable (or other expression) that was used to create it - it just knows about the value. So, for example:

    int x = 5;
    object boxed = x; // A copy of the value 5 is boxed
    x = 10; // This doesn't change the value in the box
    Console.WriteLine(boxed); // So this prints 5

    2.3.4: The small cost of boxing

    Even at the level of "hundreds of thousands" of boxing operations, in many cases performance of an application will barely be impacted. As a quick test, I wrote a program to box and unbox integers 100 million times. It took less than a second to run on my laptop (and scaled linearly as I increased the number of iterations).

    Microbenchmarks are notoriously bad indicators of overall performance for various reasons, but it's worth being guided by the kind of scale involved here. Anything which can be done over a hundred million times per second doesn't need to be avoided too much - and certainly not to the extent of bending your design out of shape without hard evidence.

    2.4.2: How many anonymous types?

    I mention in section 2.4.2 (and later in the book, of course) that if you use two anonymous type creation expressions in a single assembly, and those expressions have the same property types and names in the same order, that you get a single type.

    That's not quite true, although I doubt if you'd ever notice it. Strictly speaking, this is done on a per-netmodule basis. What's a netmodule? It's like a mini-assembly - you can build an assembly from multiple netmodule files. I'm not aware of it being commonly used, but there we go...