Welcome to MSDN Blogs Sign in | Join | Help

Optional Modifiers (or modopts) are CLR constructs that allow types to be annotated with optional information. This allows compiler writers to annotate their types with additional information that may not have a direct CLR representation. The managed C++ compiler for instance, uses modopts to represent const types.

The C# compiler does not use modopts for anything - rather, we generally use attributes to augment code. For instance, the difference between out and ref is simply an attribute applied to the parameter - out is just ref with an attribute tacked onto it. Since the CLR does not consider attributes on parameters or return types as part of the method signature, methods that differ only in out and ref look the same. This is why the C# language does not allow you to have two overloads which differ only in their out and ref-ness.

The same is not true of modopts, however. The CLR does allow you to differentiate methods by their modopts. The following type is a fragment from a perfectly legal and verifiable assembly:

.class public auto ansi beforefieldinit Modopt
       extends [mscorlib]System.Object
{
  .method public hidebysig instance void
          Foo(int32 x) cil managed
  {
    ...
  } // end of method Modopt::Foo
  .method public hidebysig instance void
          Foo(int32 modopt([mscorlib]IsConst) x) cil managed
  {
    ...
  } // end of method Modopt::Foo
  ...
} // end of class Modopt

Regardless of the fact that none of the Microsoft .NET languages allow creation of such assemblies (ie C++ does not allow you to distinguish methods only by their const-ness), the CLR allows this, and so compiler implementers that target the CLR must have some story when dealing with these assemblies.

The C# compiler does not understand any modopts. When we encounter assemblies which contain modopts, we simply ignore them and import them as if they were regular members.

When we import methods, we import each methods regardless of whether or not it has any modopts in it. We keep a note of the number of modopts in the method signature, but do not report any errors, even if there exist two imported methods which differ only in the modopts on their signature.

At overload resolution time, we consider each method whose name matches that which we're resolving, and add it to the candidate set. We then filter the candidate set based on arguments and conversions, applying the algorithm described in section 7.4.2 of the C# specification. At that point, if we're left with a candidate set in which all of the methods in the set are identical in signature with the exception of modopts, then we apply the rule that the method with the least number of modopts wins. If there is a tie between two or more methods with the minimal number of modopts, then the compiler reports an ambiguity error.

This has several ramifications.

Firstly, it allows methods with modopts not understood by the compiler to be called from the user's C# code. This has the arguable semantics of allowing the user to call a C++ function which expects a const argument with one which is not const, for instance. The philosophy of whether or not this is desirable can be argued, but by definition, these constructs are optional, so according to the definition, this behavior is perfectly acceptable.

Secondly, it allows importing assemblies which have the illegal-in-C# behavior of being overloaded only by modopts, and allows calling these methods from C# assemblies. Since the compiler doesn't understand the modopts, applying a simple deterministic heuristic seems as good behavior as one can expect.

"Why can't we do better than applying a heuristic?" you ask. Well, we could do better than that. We could note which modopt type is used for each parameter, and despite not understanding it, use it as part of the better-ness algorithm in overload resolution. That would allow the compiler to do something like the following:

In some assembly created from some other language:

modopt(MyModopt) Foo();
void Bar(modopt(MyModopt) int x) { ... }
void Bar(int x) { ... }

And then in C#, for the call Bar(Foo()) we could determine that the modopt-ed Bar should be called because the return type of Foo has the matching modopt. However, as my colleague Eric Lippert noted, "C# is not a glue language. If you want a glue-language, use VBA or something". Nicely put. :)

Last time we talked about generic method substitutions which resulted in types being declared with more than one method with identical constructed signatures. After thinking long and hard about this problem, we've come to a conclusion as to how to resolve this issue in the next release.

First, lets recap what we know about the problem.

  1. We currently produce code which causes the CLR to throw TypeLoadExceptions at runtime.
  2. Code that falls into this scenario can be easily fixed by a simple rename
  3. It is a corner case - typical coding practices will not get you into this scenario
  4. The code written is not CLS-compliant

The following is a general set of categories that can result in this scenario occurring:

// Same class
class Base<T>
{
    void Foo(T t);
    void Foo(int t);
}
 
class C : Base<int> { }
 
// Inherited from base class
class A
{
    void Foo(int i) { }
}
 
class B<T> : A
{
    void Foo(T t) { }
}
 
class C : B<int> { }
 
// Outer class
class A<T>
{
    private void Foo(T t) { }
    private void Foo(int i) { }
 
    class C : A<int> { }
}
 
// Outer class and its base class
class A<T>
{
    public void Foo(T t) { }
}
 
class B<T> : A<T>
{
    private void Foo(int i) { }
    class C : B<int> { }
}

Given the above points, we've decided to take the following action:

  1. If the user specifies that the type is CLS-compliant (via the CLSCompliantAttribute attribute), the compiler will produce a warning indicating that the constructed type is not CLS-compliant
  2. If the user does not specify CLS compliance, or specifies that the type is not CLS-compliant, then we will keep our current behavior, allow the type to be emitted, and produce an assembly that will throw a runtime exception.

This behavior will make it into the next release of the compiler. Until then, don't write code like this if you want it to work! Stay tuned for my investigations on the C# Compiler and modopts!

It's been a while since I've last written - my apologies. We've been hard at work figuring out what the next release of C# will look like, and I'm happy to say that I'm very excited about what we're working on. Great minds are at work figuring out things like integration of C# with the DLR, dynamic late binding, and better debugging experiences as we speak. Don't worry, I'll be sure to report to you about what they determine. :)

I'm always fascinated by discovering subtle details in the language that baffle me. Today I came across such a case. Consider the following code:

using System;

abstract class A<T>
{
    public abstract void Foo(T x);
}

abstract class B<T, S> : A<T>
{
    public virtual void Foo(S x){}
}

abstract class C<T, S> : B<T, S>
{
    public abstract override void Foo(T x);
}

class D : C<int, int>
{
    public override void Foo(int x) {}

    static void Main()
    {
        new D().Foo(1);
    }
}

What happens here?

Well, on the surface, this looks like it should be correct right? A's Foo and B's Foo are different, since they have different generic type parameters, and C clearly overrides A's Foo. But the weird part is D's Foo. Does it override A::Foo? Or B::Foo?

Well, it turns out that the specification outlines that when we look up members to override, we look from our current parent up - that is, when we find a method in one base class, we use that, regardless of other matches in higher ancestral classes. So since C is D's direct base class, D::Foo overrides C::Foo, which overrides the abstract A::Foo.

So we should be good here right? A::Foo has an implementation in D::Foo, and B::Foo has its own implementation as well. But we're not. We get a runtime type load exception, which complains that D has a method Foo with no body.

Well that's odd! I thought we just said that everything has an implementation!

So, being the inquisitive minded person that I am, (and of course, having a bug open to fix this issue), I went and poked around, and discovered that we could fix this problem by having the compiler emit the .override instruction, so that the CLR will know exactly which method we're overriding.

Well neat, that fixes that problem. Or so I thought!

Bug number two looks a little different, but very similar.

using System;

class A<T>
{
    protected virtual void Foo(T x){}
}

class B<T, S> : A<T>
{
    public virtual void Foo(S x) {}
}

class C<T, S> : B<T, S>
{
    protected override void Foo(T x) { }
}

class D : C<int, int>
{
    protected override void Foo(int x) {}
    
    static void Main()
    {
        new D();
    }
}

In this scenario, the compiler again allows us to compile the code without problems, and once again we get a runtime exception. This time, we get an exception saying that we're attempting to lower the access of B::Foo. Interesting. This looks like my first bug! So what do I do? I apply the fix to my first bug and see what happens.

No luck.

"But why not?", you ask? Well, after more digging (and finding the right contacts on the CLR team to chat with), we discovered that the initial fix was wrong! It turns out that by specifying the .override command, we tell the CLR to override A::Foo, but the CLR also finds B::Foo and tells D:Foo to override that as well.

At the end of it all, we discovered that the entire scenario is an error. Why? The CLI spec, chapter 11.9.9 "Inheritance and overriding" outlines:

Type definitions are invalid if, after substituting base class generic arguments, two methods result in the same name and signature (including return type). The following illustrates this point:
[Example:
.class B`1<T>
{ .method public virtual void V(!0 t) { … }
.method public virtual void V(string x) { … }
}
.class D extends B`1<string> { } // Invalid
Class D is invalid, because it will inherit from B<string> two methods with identical signatures:
void V(string)

At the end of it all, we discovered that we have a disconnect between the CLR and the C# compiler here. We're currently working on determining what C# spec changes we'll need to make to clarify this case, and from there we'll put a fix together.

So what did I learn today? Two things:

1) Our customer that found this bug (I only know them as nikov) is great at finding an exploit, and then massaging it in all possible ways to find other problems (which I might add, is phenomenal).

2) People should just not code like this cause its confusing and doesn't work :)

On December 19th, we officially announced that VS 2008 is released to manufacturing! This is the first release of the product (of any product for that matter) that I've worked heavily on, so I'm quite anxious to see the feedback on the work that we've done.

Aside from the obvious LINQ work that we've created for 2008, we've introduced several key new language features, and have done quite a few things under the hood as well, for both the compiler and the IDE. The introduction of lambdas mixed with method type inference was one of the primary reasons that we decided to rework the internal binding engine used by the IDE, and it was this work that I was primarily involved in. I got to have a hand in:

1) Re-tooling the binding engine so that it could be consumed by both the compiler and the IDE.

2) Re-tooling the Refactoring engine so that its results were consistent with the results used by the IDE for Intellisense.

3) Getting Intellisense support for all C# 3.0 language features, including lambdas and queries.

As you can see, when you fire up the editor and start coding away, you'll be using a lot of the stuff that I've worked on, so I'm hoping I've made y'all proud. I'm rather excited about the product, and hope that you guys'll give it a whirl. And please, by all means, tell me what you think of it!

Technorati Tags:

One of the things that the language designers considered when designing the C# language was the ability to notify external callers of certain events happening. To solve this problem, they (surprise surprise) introduced the event construct.

One of the oddities in the design however, comes in the form of virtual events. This is one of those design decisions that we recognize is something we would like to change, but as my colleague, Eric Lippert explains in a series of posts concerning breaking changes, we aren't able to fix everything that we would like to.

How virtual events work

So first off, lets quickly describe how virtual events work. Section 10.8.4 of the spec describes this for us:

A virtual event declaration specifies that the accessors of that event are virtual. The virtual modifier applies to both accessors of an event.
The accessors of an inherited virtual event can be overridden in a derived class by including an event declaration that specifies an override modifier. This is known as an overriding event declaration. An overriding event declaration does not declare a new event. Instead, it simply specializes the implementations of the accessors of an existing virtual event.

Notice that an overriding event does not declare a new event. Let's now quickly refresh field-like events and consider how they work in conjunction with virtual and overriding events. They are described in 10.8.1:

Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. To be used in this way, an event must not be abstract or extern, and must not explicitly include event-accessor-declarations. Such an event can be used in any context that permits a field. The field contains a delegate (§15) which refers to the list of event handlers that have been added to the event. If no event handlers have been added, the field contains null.

Declaring a virtual field-like event then, will cause the compiler to generate a delegate field to back the event, and virtual accessors for the event. Declaring an overriding field-like event will not cause the compiler to generate a new backing field for the overriding event in the case of field-like events, but will cause it to generate overriding accessors.

A concrete example of virtual and overriding field-like events

Now, consider some parent class P which declares a virtual event, and some derived class D which overrides it. First, note that we have four combinations:

  1. P declares a field-like event, and D declares a field-like event
  2. P declares a field-like event, and D declares a user-defined event
  3. P declares a user-defined event, and D declares a field-like event
  4. P declares a user-defined event, and D declares a user-defined event

In case (1), P contains a delegate field backing the event, and two virtual accessors that add and remove from the delegate. D contains two overriding accessors, who add and remove from the delegate contained in P. Notice that for this to work, the delegate in P must be elevated from private to protected.

In case (2), P contains a delegate field and two accessors, and D contains the two user-defined overloaded accessors. D does not have access to the field contained in P.

In case (3), P contains the two virtual user-defined accessors, and D contains a delegate field, along with two overriding accessors that add and remove from the backing field.

In case (4), P contains the two virtual user-defined accessors, and D contains the two overriding user-defined accessors.

Here's the code for it:

class P
{
    public virtual event EventHandler case1_event;
    public virtual event EventHandler case2_event;
    public virtual event EventHandler case3_event
    {
        add { }
        remove { }
    }
    public virtual event EventHandler case4_event
    {
        add { }
        remove { }
    }
}

class D : P
{
    // D has access to P.case1_event's backing field.
    public override event EventHandler case1_event;

    // D does not have access to P.case2_event's backing field.
    public override event EventHandler case2_event
    {
        add { }
        remove { }
    }

    // D has a backing field generated for case3_event, which is private
    public override event EventHandler case3_event;

    // This is just the typical virtual/override pattern.
    public override event EventHandler case4_event
    {
        add { }
        remove { }
    }
}
A bug in the compiler

The current C# compiler (in Visual Studio 2008 Beta2) has a bug when dealing with scenario (1) above. Consider the following scenario:

class P
{
    public virtual event EventHandler myEvent;
    public void parentEventCall()
    {
        myEvent(this, null);
    }
}
class D : P
{
    public override event EventHandler myEvent;
    public void derivedEventCall()
    {
        myEvent(this, null);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Create an instance of D, and create a P reference to it.
        D derived = new D();
        P parent = derived;

        // Hook a handler up through the derived and parent references.
        derived.myEvent += new EventHandler(derivedHandler);
        parent.myEvent += new EventHandler(parentHandler);

        // Fire both events.
        derived.derivedEventCall();
        parent.parentEventCall();

    }
    static void parentHandler(object sender, EventArgs e)
    {
        Console.WriteLine("parent handler");
    }

    static void derivedHandler(object sender, EventArgs e)
    {
        Console.WriteLine("derived handler");
    }
}

Now, we would expect this code to output the sequence of derived/parent/derived/parent handlers being fired, because in this scenario, we should have one backing field for both the virtual event and the overriding one, so both of the trigger calls should act upon the same event, and both of the handlers should be hooked onto the same event.

However, if you go ahead and paste this code into Visual Studio, compile it, then run it, you'll get a NullReferenceException being thrown. Debugging it will show us two thing:

Firstly, the compiler does not generate a protected backing field on the parent class P. Instead, it generates two private backing fields - one in P and one in D.

Secondly, when we execute both handler hookups, we'll see that it is the derived delegate field that gets both of the handlers hooked up to it.

When we step into parentEventCall then, we'll notice that the this pointer is of type P, and that the only visible backing field is P.myEvent, which is null. Attempting to trigger the delegate then throws us the NullReferenceException, as expected.

The work-around

The simple work-around for the issue is to use a virtual method for triggering the event as well. An easy to remember rule of thumb is that if you have a virtual event, have a virtual triggering method. If you override a virtual event, override its trigger method as well.

A quick argument for not-fixing this issue

There are two main reasons that prompted us to choose to not fix this issue.

The first is the common practice for declaring event trigger methods. Whenever you write a trigger method, you must do a null check - if you don't and someone calls your trigger method without ever adding a handler, you'll get a NullreferenceException. This means that anyone applying good coding practices will already be safeguarded from this erroneous exception being thrown.

However, fixing this issue will be a breaking change. Code that used to never execute because the backing field in the parent class was always null will now execute because the backing field is the same in both the parent and derived classes, and will have a value once a handler is added. This is undesirable.

Secondly, there really is no need to be in this scenario in the first place. Using a virtual field-like event in the parent class and not changing any of its behavior in the overriding derived class is not necessary. You can simply omit the override in the derived class to get the desired behavior.

kick it on DotNetKicks.com

In my previous post, Compiler-generated scopes for local variable declarations, I briefly touched on the issue of multiple meanings applied to the same name. In this post, I'll aim to flush out the compiler's rules with regards to binding names in their local scopes.

Simple name resolution

First, lets recall the spec's definition of simple name resolution, from section 7.5.2:

If [...] the simple-name appears within a block and if the block’s (or an enclosing block’s) local variable declaration space (§3.3) contains a local variable, parameter or constant with name I, then the simple-name refers to that local variable, parameter or constant and is classified as a variable or value.

Simply put, the simple name gets resolved to whatever is declared inside its current block, regardless of whether or not there already exists a declaration of the name outside of the block. So consider the following:

class C
{
    public int y;

    void Foo()
    {
        int x;
        x = 0; // (1) This binds to the local variable defined above.
        y = 0; // (2) This binds to the field y.

        {
            x = "s"; // (3) This binds to the local defined below.
            string x;

            y = "s"; // (4) This binds to the local defined below.
            string y;
        }
    }
}

Notice that both (3) and (4) produce compiler errors, as well as the redeclarations of x and y on the lines following (3) and (4) respectively. This is in accordance with section 5.1.7 of the spec:

Within the scope of a local variable introduced by a local-variable-declaration, it is a compile-time error to refer to that local variable in a textual position that precedes its local-variable-declarator. If the local variable declaration is implicit (§8.5.1), it is also an error to refer to the variable within its local-variable-declarator.
Within the scope of a local variable, it is a compile-time error to refer to the local variable in a textual position that precedes the local-variable-declarator of the local variable.
Locals preceding their declarator

Lets unpack this. First, lets quickly note that it is an error to refer to a local variable in a textual position that precedes its declarator. However, referring to it inside its declarator is permitted if the local variable is not implicitly typed. That means that the following is true:

    int t = (t = 5); // OK
    var s = (s = 10); // Error

In the first statement, by the time we attempt to bind the right hand side of the assignment statement, we've already declared that t is of type int. We can then bind the right hand side successfully with that knowledge, and then bind the assignment to the left hand side variable, which is the variable initializer. However, in the second statement, we do not have a type for s initially, so when we bind the right hand side of the assignment, we cannot determine if 10 is assignable to s. Consider the following:

    var t = (Foo() ? t = "test": t = 15);

What would t's type be? We cannot report convertibility errors on the right hand side for the two branches of the ternary because we don't have a type for t to report convertibility errors on.

We therefore decided simply to disallow this scenario by disallowing the usage of the local variable in its declarator.

Name Hiding?

We should note that name hiding is only allowed on fields that have not been referenced in the current scope. For instance:

class C
{
    int x;
    int r;
    void Foo(int y)
    {
        int z;
        int s;

        // Legal - x has not been used in this context yet.
        string x = "s";

        // Illegal - cannot hide parameters.
        string y;

        // Illegal - cannot declare two locals of the same 
        // name in the same scope.
        string z;

        r = 10;
        {
            // Illegal - r has already been used in the parent 
            // scope, so cannot redefine it.
            string r = "s";

            // Illegal - cannot hide locals.
            string s = "s";
        }
    }
}

Notice that the only legal hiding action is the first one - you are allowed to redefine x to be a string, because it has not been referenced as a field in any containing scope for the method Foo.

When is it an error?

As you can imagine, this notion of using a name before its declarator will cause the compiler to generate some errors when the situation occurs. Let me first outline the different situations that this can occur, and give quick examples.

class C
{
    int x;
    int y;
    int z;
    void Foo()
    {
        x = 10; // (1) Binds to C.x
        {
            // (2) Binds to local variable declared below. 
            // Error - usage before declaration.
            x = 10;

            // (3) Error, cannot redefine x because x has been used.
            string x; 
        }

        // (4) Binds to local y. Error, usage before declaration.
        y = 10;
        string y;

        Func<int, int> f = x => x + 1; // (5) Error, cannot redefine x.
        Func<int, int> g = z => z + 1; // (6) OK. z has not been used.
    }
}

The general rule of thumb is that the name will always resolve to the local variable declared in the current scope if there is one, regardless of whether or not the name has been used to mean anything else in any scopes above it. In (2) above, one might think that x would bind to C.x, just like (1) did, but the spec is clear on this point - the name will always resolve to the local variable bound in the closest scope.

2005 C# Compiler vs. 2008 C# Compiler

One thing that is worth mentioning is that in the 2008 C# Compiler, we fixed the error that is reported to be more in line with the specification. Consider the following code:

class C
{
    void Foo()
    {
        int x;
        {
            // (1)
            // 2005 Compiler compiles this statement without errors.
            // 2008 Compiler yields CS0841: Cannot use variable 'x' 
            // before it is declared
            x = 5;

            // (2) 
            // 2005 Compiler yields CS0029: Cannot implicitly 
            // convert type 'string' to 'int'
            // 2008 Compiler yields CS0841: Cannot use variable 'x' 
            // before it is declared
            x = "s";

            // (3)
            // 2005 and 2008 Compilers yield CS0136: A local variable 
            // named 'x' cannot 
            // be declared in this scope because it would give a 
            // different meaning to 'x', which is already used in a 
            // 'parent or current' scope to denote something else
            string x;
        }
    }
}

In the 2005 C# Compiler, we incorrectly bound both usages of x to the outer local variable, and so we would bind (1) perfectly fine, and we would report an error on (2) saying that string is not convertible to int. We would also report an error on (3), saying that you cannot redeclare x to be something else.

In the 2008 C# Compiler, we fix this to correctly reflect the spec. Both (1) and (2) bind to (3), and since they are textually before their declaration, they both yield an error, saying that they are being used before they are declared. (3) also yields an error saying that you cannot redeclare x.

So what can we conclude?

Well I hope that helps clarify things a bit. I think the real thing that we should be concluding from this little exercise is that we should be choosing better names for our variables and fields! Descriptive names would help avoiding name clashing issues - if they don't, it's probably a sign that some refactoring is in order!

kick it on DotNetKicks.com

I was tasked with understanding and fixing a bug on error reporting with foreach iteration variables the other day, and it got me thinking about local variable scoping rules in C# in general. First, the bug.

The Bug

Consider the following code:

class C
{
static void Main(string[] args)
{
foreach (int myCollection in myCollection)
{
// Your code here
}
}
}

This code should clearly not compile, because myCollection is used before it is declared. But it does! On VS2008 Beta2, this code currently compiles, and on runtime, produces a TypeLoadException.

What happened?

Well, first let us consider how a foreach statement is expanded. According to the C# language specification section 8.8.4, the foreach statement is expanded as follows:

E enumerator = (collection).GetEnumerator();
try
{
while (enumerator.MoveNext())
{
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally
{
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}

Notice that the call to "collection.GetEnumerator" happens outside of the try scope and the while scope, but the definition of the iteration variable "element" occurs inside the while scope. The bug was that when we attempted to bind the collection to get the GetEnumerator call off of it, we bound it inside the while scope instead of outside the try scope. As such, when we asked the local symbol table to resolve the name "myCollection", it returned us the iteration variable declared inside the while scope. This caused the compiler to incorrectly accept this program and not produce an error when compiling it. When one tries to run the program however, the CLR detects the problem with the type, and consequently throws the TypeLoadException, as expected.

The fix was simply to move the binding of the collection outside the try scope, and correctly report that "myCollection" is not defined in the outer scope.

Scoping rules

That got me thinking about scoping rules in general. Where do we introduce local variables and scopes that may not be intuitive to the user? What exactly are our local variable scoping rules to begin with?

I'll deal with the latter (important!) question in a subsequent post.

In addition to variable declaration statements, the language provides four other mechanisms to declare local variables.

  1. Foreach iteration variables
  2. Lambda parameters/Anonymous method parameters
  3. Catch exception variables
  4. Using statement variables

For those interested, I'll briefly describe how the remaining three mechanisms declare their locals, and how they are scoped.

Lambda Parameters

Lambda parameters work as one would expect - the parameters are declared as local variables inside the scope of the body of the lambda. From section 7.14.1 of the C# language specification:

The optional anonymous-function-signature of an anonymous function defines the names and optionally the types of the formal parameters for the anonymous function. The scope of the parameters of the anonymous function is the anonymous-function-body. (§3.7) Together with the parameter list (if given) the anonymous-method-body constitutes a declaration space (§3.3). It is thus a compile-time error for the name of a parameter of the anonymous function to match the name of a local variable, local constant or parameter whose scope includes the anonymous-method-expression or lambda-expression.

In essence, the last sentence in that statement says that you cannot declare a parameter of a lambda or anonymous method (I'll refer to the two simply as the lambda) which has the same name as any local variable in the scope of the lambda's declaration. Why? Because it would give a different meaning to the name inside the body of the lambda. I'll elaborate on this more in my next post.

Catch Blocks

Catch blocks that declare local variables are scoped for the lifetime of the catch block:

When a catch clause specifies both a class-type and an identifier, an exception variable of the given name and type is declared. The exception variable corresponds to a local variable with a scope that extends over the catch block.

Any exception variables declared in the catch block must have a type that is System.Exception, is derived from System.Exception, or is a type parameter that has System.Exception (or a subclass thereof) as its effective base class.

Using Statements

Using statements work pretty much as expected as well:

{
ResourceType resource = expression;
try
{
statement;
}
finally
{
if (resource != null) ((IDisposable)resource).Dispose();
}
}

Note that for using statements, the local variable that is declared for the resource acquisition is read-only. It is a compile time error to attempt to modify any local variables declared in this manner. Note also that a using statement that acquires more than one resource is really syntactic sugar for nested using statements, and is bound as a series of nested try blocks.

Any resource acquisition variables must be of a type that implements System.IDisposable.

kick it on DotNetKicks.com

Hi there. My name is Sam Ng, and I'm a developer on the C# compiler team. This here's my little outlet of random stuff that I think about, and things that I learn as I dig deeper into the wonderful world of software.

Who am I?

I'm currently a developer on the C# compiler team. I interned on the team in 05, graduated from the University of Waterloo in 06, and started working full time on the team after a brief European excursion. I'm a pretty avid motorcyclist and snowboarder, so those'll probably be the two analogies or examples that I'll use most often.

What is this blog about?

This blog is my outlet for anything technical that I may be thinking about, reading about, learning about, or working on. I'll try to keep it updated regularly, and will try my best to answer any questions you may have.

 
Page view tracker