(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
Since the new C# 3.0 language features were unveiled last week at PDC, there has been a lot of commentary, much of which is untainted by such prosaic concerns as bothering to check the facts before ranting.
A particularly popular target for uninformed complaint is the new var
keyword. This lets
you declare a variable without saying what the variable's type is. For example:
var ivar = 42;
Many developers of a certain background leap to the wrong conclusion when
they see this. (Particularly if they're familiar with the identically spelled but utterly
different var
keyword in JScript.)
Here's a fairly typical reaction from someone on the DOTNET-CX mailing list:
"What's wrong with 'Object'?"
This presupposes, incorrectly, that the code above means the same as this snippet:
object iobj = 42;
In fact they are completely different. For example, given these two declarations, this compiles fine:
ivar += 10;
whereas this:
iobj += 10;
causes a compiler error:
error CS0019: Operator '+=' cannot be applied to operands of type 'object' and 'int'
Likewise, consider these two lines
int i1 = ivar; int i2 = iobj;
The first line compiles just fine. The second causes this error:
error CS0266: Cannot implicitly convert type 'object' to 'int'. An explicit conversion exists (are you missing a cast?)
And there are things a variable of type object
will let us do that a var
won't. A particular variable of
type object
can hold different types over its lifetime. So even though it was initialized with an integer, we can make it
hold something else later:
iobj = "Hello";
But try that with the var
:
ivar = "world!";
and you get this error:
error CS0029: Cannot implicitly convert type 'string' to 'int'
It looks like compiler is behaving as though the type of ivar
is int
.
And that's exactly what's happening. Let's look at some IL - always a
good way to work out what's going on. Consider this method:
static void Main(string[] args) { var ivar = 42; object iobj = 42; }
It compiles to the following IL:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 13 (0xd) .maxstack 1 .locals init ([0] int32 ivar, [1] object iobj) IL_0000: nop IL_0001: ldc.i4.s 42 IL_0003: stloc.0 IL_0004: ldc.i4.s 42 IL_0006: box [mscorlib]System.Int32 IL_000b: stloc.1 IL_000c: ret } // end of method Program::Main
Notice that ivar
is an int32
. (Look at the .locals
section.)
That's IL for System.Int32
, or as we usually say
in C#, int
. But iobj
is of type object
.
(Aka System.Object
, or as we say in C#, object
.)
So if you bother to compile up an example (or read the C# 3.0 spec) it's instantly pretty clear that var
is very much
not the same thing as object
.
Local variables in C# are statically typed. C# 3.0 doesn't change this. The only thing that var
changes is that
you don't have to declare the type explicitly - the compiler infers the variable's type. Specifically, the variable's type will
be whatever type its initialization expression is. (An uninitialized var
will cause a compiler error.) For example, our ivar
variable above was initialized with the
expression 42
. That's an integer literal, so the expression's type is System.Int32
, which in turn means
that ivar
's type is System.Int32
. The compiler worked out
the type for itself, but it's still statically typed. It's as though we had just
declared the variable as an int
.
I guess some of the confusion arises from the fact that a lot of people are under the mistaken impression that static typing and manifest typing are the same thing. It's easy to fall into that trap if all you've seen is the C family of languages and dynamic languages like ECMAScript (JScript), Python or Ruby. But if you've ever used ML, you'll be well aware that it's perfectly possible for a language to employ static typing without manifest typing. (Manifest typing is where you have to declare the types of your functions or variables explicitly. By contrast, static typing is where the types of variables and expressions are all determined at compile time, and where type checking is also done at compile time.)
You can see this in action at an interactive ML prompt. Here are a couple of variable declarations (the "-" is the prompt by the way; stuff on lines beginning with "-" has been typed in, the rest is what ML printed out):
- val foo = 42; val foo = 42 : int - val bar = 42.0; val bar = 42.0 : real
Note how ML has shown us the type it has inferred for each variable. C# does pretty
much the same thing when you use var
. It doesn't print out any messages
explicitly telling us that this is what it did of course - there isn't a C# prompt! But it's performing a similar process at compile time.
Incidentally, ML can infer much more interesting stuff, like this:
- fun apply x y = x y; val apply = fn : ('a -> 'b) -> 'a -> 'b
Here, I've defined a function called apply
that takes a couple of parameters. ML has looked at what my
apply
function does: apply
assumes the first parameter (x)
is a function and evaluates it by passing in the second parameter (y). ML's type
inference system has worked this out and inferred a type for apply
that reflects this. If you're not familiar with ML, rather than talk
you through ML's type declaration syntax, I'll just write the equivalent C# function declaration:
public static T2 apply<T1, T2>(System.Converter<T1, T2> x, T1 y) ...
C# won't infer function declarations for you like this. I just mention the fact that ML can do it to emphasize the point that you don't need explicit type declarations (manifest typing) to get static typing. (Also, I felt I should point out that ML's type inference is a lot more powerful than C#'s - I didn't want to give a false impression of ML...)
The main point is that C# variables declared with var
have a static type, and are subject to exactly the same
rules as variables whose type is declared explicitly.
Why add this feature to C#? It offers a couple of benefits, one of which is the real reason, and the other of which is really just a useful side-effect. The useful side effect is that you don't have to specify the type name twice. No more of this tedious stuff:.
Dictionary<string, Control> d = new Dictionary<string, Control>();
Once is now enough:
var d = new Dictionary<string, Control>();
But that's not the real reason. That's just a bonus. The real reason var
was added is so that we can declare
a strongly typed variable without needing to know the name of the variable's type. This is required in order to enable
another new C# 3.0 language
feature: anonymous types. You wouldn't be able to declare a variable of an anonymous
type if you always had to include the type name as part of the variable declaration.
That's the main reason var
has been added to the language.
But what are anonymous types, and why are they useful? That would be a topic for another blog entry...
[Update: And var
isn't a variant either.]