What’s the difference between “as” and cast operators?

Most people will tell you that the difference between (Alpha) bravo and bravo as Alpha is that the former throws an exception if the conversion fails, whereas the latter produces null. Though this is correct, and this is the most obvious difference, it’s not the only difference. There are pitfalls to watch out for here.

First off, since the result of the as operator can be null, the resulting type has to be one that includes a null value: either a reference type or a nullable value type. You cannot do x as int, that doesn’t make any sense. If the operand isn’t an int, then what should the expression’s value be? The type of the as expression is always the type named in the expression, so it needs to be a type that can take a null.

Second, the cast operator, as I’ve discussed before, is a strange beast. It means two contradictory things: “check to see if this object really is of this type, throw if it is not” and “this object is not of the given type; find me an equivalent value that belongs to the given type”. The latter meaning of the cast operator is not shared by the as operator. If you say

short s = (short)123;
int? i = s as int?;

then you’re out of luck. The as operator will not make the representation-changing conversions from short to nullable int like the cast operator would. Similarly, if you have class Alpha and unrelated class Bravo, with a user-defined conversion from Bravo to Alpha, then (Alpha) bravo will run the user-defined conversion, but bravo as Alpha will not. The as operator only considers reference, boxing and unboxing conversions.

And finally, of course the use cases of the two operators are superficially similar, but semantically quite different. A cast communicates to the reader “I am certain that this conversion is legal and I am willing to take a runtime exception if I’m wrong”. The as operator communicates “I don’t know if this conversion is legal or not; we’re going to give it a try and see how it goes”.


Notes from 2020

There were a number of pragmatic comments to the original post:

  • There is pressure to use as over casting for legibility reasons; (x as C).M() is perceived as easier to read than ((C)x).M(). Indeed, the cast operator has other syntactic problems than just being ugly; is (C)-1 casting -1 to C, or subtracting 1 from (C)?
  • It seems like there is a missed opportunity for a syntactic sugar that both tests the type and gives you a value of that type in one step; those comments and similar comments from many other users eventually led to if (x as C c) being added to the language.
  • One commenter pointed out that use of type checking operators in generic code is a bad smell; I thoroughly agree.
  • Another pointed out that any sort of type checking at runtime is an indication that the programmer was unable to produce a proof of type safety that could be checked by the compiler, and is therefore suspect. It would be nice if those points were easier to find quickly in a program because they need deeper code review. This is an often neglected aspect of language design; I should write more about that!
  • There was also a long conversation about the use of various operators for different kinds of dynamic dispatch. I discussed these in my Wizards and Warriors series in more detail.

String interning and String.Empty

Here’s a curious program fragment:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // false !?

Surely if A equals B, and B equals C, then A equals C; that’s the transitive property of equality. It appears to have been thoroughly violated here.
Continue reading

What’s the difference between conditional compilation and the Conditional attribute?

User: Why does this program not compile correctly in the release build?

class Program 
{ 
#if DEBUG 
    static int testCounter = 0; 
#endif 
    static void Main(string[] args) 
    { 
        SomeTestMethod(testCounter++); 
    } 
    [Conditional("DEBUG")] 
    static void SomeTestMethod(int t) { } 
}

Eric: This fails to compile in the release build because testCounter cannot be found in the call to SomeTestMethod.

User: But that call site is going to be omitted anyway, so why does it matter? Clearly there’s some difference here between removing code with the conditional compilation directive versus using the conditional attribute, but what’s the difference?

Eric: You already know the answer to your question, you just don’t know it yet. Let’s get Socratic; let me turn this around and ask you how this works. How does the compiler know to remove the method call site?

User: Because the method called has the Conditional attribute on it.

Eric: You know that. But how does the compiler know that the method called has the Conditional attribute on it?

User: Because overload resolution chose that method. If this were a method from an assembly, the metadata associated with that method has the attribute. If it is a method in source code, the compiler knows that the attribute is there because the compiler can analyze the source code and figure out the meaning of the attribute.

Eric: I see. So fundamentally, overload resolution does the heavy lifting. How does overload resolution know to choose that method? Suppose hypothetically there were another method of the same name with different parameters.

User: Overload resolution works by examining the arguments to the call and comparing them to the parameter types of each candidate method and then choosing the unique best match of all the candidates.

Eric: And there you go. Therefore the arguments must be well-defined at the point of the call, even if the call is going to be removed. In fact, the call cannot be removed unless the arguments are extant! But in the release build, the type of the argument cannot be determined because its declaration has been removed.

So now you see that the real difference between these two techniques for removing unwanted code is what the compiler is doing when the removal happens. At a high level, the compiler processes a text file like this. First it “lexes” the file. That is, it breaks the string down into “tokens” — sequences of letters, numbers and symbols that are meaningful to the compiler. Then those tokens are “parsed” to make sure that the program conforms to the grammar of C#. Then the parsed state is analyzed to determine semantic information about it; what all the types are of all the expressions and so on. And finally, the compiler spits out code that implements those semantics.

The effect of a conditional compilation directive happens at lex time; anything that is inside a removed #if block is treated by the lexer as a comment. It’s like you simply deleted the whole contents of the block and replaced it with whitespace. But removal of call sites depending on conditional attributes happens at semantic analysis time; everything necessary to perform that semantic analysis must be present. 

User: Fascinating. Which parts of the C# specification define this behavior?

Eric: The specification begins with a handy “table of contents”, which is very useful for answering such questions. The table of contents states that section 2.5.1 describes “Conditional compilation symbols” and section 17.4.2 describes “The Conditional attribute”.

User: Awesome!

What’s the difference? fixed versus fixed

I got an email the other day that began:

I have a question about fixed sized buffers in C#:

unsafe struct FixedBuffer 
{ 
  public fixed int buffer[100];

Now by declaring buffer as fixed it is not movable…

And my heart sank. This is one of those deeply unfortunate times when subtle choices made in the details of language design encourage misunderstandings.

When doing pointer arithmetic in unsafe code on a managed object, you need to make sure that the garbage collector does not move the memory you’re looking at. If a collection on another thread happens while you’re doing pointer arithmetic on an object, the pointer can get all messed up. Therefore, C# classifies all variables as “fixed” or “movable”. If you want to do pointer arithmetic on a movable object, you can use the fixed keyword to say “this local variable contains data which the garbage collector should not move.” When a collection happens, the garbage collector needs to look at all the local variables for in-flight calls (because of course, stuff that is in local variables needs to stay alive); if it sees a “fixed” local variable then it makes a note to itself to not move that memory, even if that fragments the managed heap. (This is why it is important to keep stuff fixed for as little time as possible.) So typically, we use “fixed” to mean “fixed in place”.

But that’s not what “fixed” means in this context; this means “the buffer in question is fixed in size to be one hundred ints” — basically, it’s the same as generating one hundred int fields in this structure.

Obviously we often use the same keyword to mean conceptually the same thing. For example, we use the keyword internal in many ways in C#, but all of them are conceptually the same. It is only ever used to mean “accessibility to some entity is restricted to only code in this assembly”.

Sometimes we use the same keyword to mean two completely different things, and rely upon context for the user to figure out which meaning is intended. For example:

var results = from c in customers where c.City == "London" select c;

versus

class C<T> where T : IComparable<T>

It should be clear that where is being used in two completely different ways: to build a filter clause in a query, and to declare a type constraint on a generic type parameter.

We cause people to run into trouble when one keyword is used in two different ways but the difference is subtle, like our example above. The user’s email went on to ask a whole bunch of questions which were predicated on the incorrect assumption that a fixed-in-size buffer is automatically fixed in place in memory.

Now, one could say that this is just an unfortunate confluence of terms; that “fixed in size” and “fixed in place” just happen to both use the word “fixed” in two different ways, how vexing. But the connection is deeper than that: you cannot safely access the data stored in a fixed-in-size buffer unless the container of the buffer has been fixed in place. The two concepts are actually quite strongly related in this case, but not at allthe same.

On the one hand it might have been less confusing to use two keywords, say pinned and fixed. But on the other hand, both usages of fixed are only valid in unsafe code. A key assumption of all unsafe code features is that if you are willing to use unsafe code in C#, then you are already an expert programmer who fully understands memory management in the CLR. That’s why we make you write unsafe on the code; it indicates that you’re turning off the safety system and you know what you’re doing.

A considerable fraction of the keywords of C# are used in two or more ways: fixed, into, partial, out, in, new, delegate, where, using, class, struct, true, false, base, this, event, return and void all have at least two different meanings. Most of those are clear from the context, but at least the first three — fixed, into and partial — have caused enough confusion that I’ve gotten questions about the differences from perplexed users. I’ll take a look at into and partial next.

Arrays of arrays

Most C# developers understand that there’s a difference between a “rectangular” and a “ragged” two-dimensional array.

int[,] rectangle = { 
  {10, 20}, 
  {30, 40}, 
  {50, 60} }; 
int[][] ragged = { 
  new[] {10},
  new[] {20, 30},
  new[] {40, 50, 60} };

Here we have a two-dimensional array with six elements, arranged in three rows of two elements each. And we have a one-dimensional array with three elements, where each element is itself a one-dimensional array with one, two or three elements.

That’s all very straightforward. Where things get brain-destroying is when you try to make a ragged array of two-dimensional arrays.  Continue reading

Alas, Smith and Jones

We have a feature in C# which allows you to declare a “friend assembly“. If assembly Smith says that assembly Jones is its friend, then code in Jones is allowed to see “internal” types of Smith as though they were public. Fully trusted assemblies of course can always use reflection to see private and internal details of other assemblies; that is what “full trust” means. And in the new CLR security model, two assemblies that have the same grant set can see each other’s private and internal details via reflection. But friend assemblies are the only mechanism that allow compile time support for peering at the internal details of another assembly. It’s a pretty handy feature for building a “family” of assemblies that share common implementation details.

The compiler enforces one particularly interesting rule about the naming of friendly assemblies. If assembly Smith is a strong-named assembly, and Smith says that assembly Jones is its friend, then Jones must also be strong-named. If, however, Smith is not strong-named, then Jones need not be strong-named either.

I’m occasionally asked “what’s up with that?”

When you call a method in Smith and pass in some data, you are essentially trusting Smith to (1) make proper use of your data; the data might contain sensitive information that you don’t want Smith to misuse, and (2) take some action on your behalf. In the non-computer world an entity which you share secrets with and then empower to take actions on your behalf is called an “attorney”. (Or “delegate”, which obviously I want to avoid because that means something technical in C#.)

That got me thinking, which usually spells trouble.

You want to hire an attorney. You go to Smith & Jones, Esqs. You meet with Smith, the trustworthy-looking man on the left of this photo:

alassmithandjones_2_396x222

Scenario (1):

You check Smith’s ID. It really is Mel Smith. You are contemplating giving Smith a secret, and empowering Smith to act on your behalf with the knowledge of that secret. Smith says “I share my secrets with my partner Griff Rhys Jones, whose identity you may now verify, here is Jones and here is Jones’ ID. Jones will also be acting on your behalf. Jones has full access to all secrets which are internal to this organization.”

You decide that you trust both Smith and Jones, so you share your secrets with Smith. A series of wacky sketch comedy hijinks then ensues.

(Smith and Jones are both strong-named assemblies. That is, they have “ID” you can verify. Smith’s internals are visible to Jones. Everything is fine.)

Scenario (2):

You check the ID. It is Mel Smith. Smith says “By the way, I share my internal secrets with everyone in the world who claims their name is Jones, I don’t bother to check IDs, I just trust ’em, and you should too! Everyone named Jones is trustworthy! Oh, and that includes my partner over here, Jones. Good old Jones! No, you can’t check Jonesie’s ID. Doesn’t have any ID, that Jones.”

This is ludicrous. Obviously this breaks the chain of trust that you showed you were looking for when you checked Smith’s ID in the first place.

(The compiler keeps you from getting into this terrible situation by preventing Smith from doing that; Smith, a strong-named assembly, can only state that it is sharing his internal details with another strong-named assembly. Smith cannot share its internals with any assembly named Jones, a weakly-named assembly in this scenario. We restrict Smith from exposing internals to weakly-named assemblies so that Smith does not accidentally create this security hole or accidentally mislead you into believing that Smith’s internals are in any way hidden from partially-trusted code.)

Scenario (3):

You don’t bother to check Smith’s ID. In fact, you give your secrets and power of attorney to anyone named Smith, regardless of who they actually are or where they came from. The first Smith you pick off the street says “By the way, I’ll give my internal secrets to anyone named Jones”.

Do you care?  Why would you?  You’re already giving secrets away to anyone named Smith! If a con artist wants your secrets, he can pretend to be Jones and take them from Smith, or he can pretend to be Smith and get them from you directly, but either way, the problem is that you are giving away secrets and power of attorney to strangers off the street.

(Smith and Jones are both weakly named assemblies, Smith exposes internals to Jones. This is perfectly legal, because if you don’t care enough about who you’re talking to to check IDs then what’s the point of the security system preventing those random strangers from talking to each other?)

Reserved and contextual keywords

Many programming languages, C# included, treat certain sequences of letters as “special”.

Some sequences are so special that they cannot be used as identifiers. Let’s call those the “reserved keywords” and the remaining special sequences we’ll call the “contextual keywords”. They are “contextual” because the character sequence might one meaning in a context where the keyword is expected and another in a context where an identifier is expected.[1. An unfortunate consequence of this definition is that using is said to be a reserved keyword even though its meaning depends on its context; whether the using begins a directive or a statement determines its meaning. And similarly for other keywords that have multiple usages, like fixed.]

The C# specification defines the following reserved keywords:

Continue reading

Locks and exceptions do not mix

A couple years ago I wrote a bit about how our codegen for the lock statement could sometimes lead to situations in which an unoptimized build had different potential deadlocks than an optimized build of the same source code. This is unfortunate, so we’ve fixed that for C# 4.0. However, all is still not rainbows, unicorns and Obama, as we’ll see.
Continue reading