IDisposable and "using" Still Seem Wrong to Me...



  • I posted this comment in an earlier thread but it was locked (for being old, I think).

    I made several points about garbage collection in .NET in that thread. I have read everyone's replies, and for the most part I am willing to concede that the designers of .NET and C# had good reasons for what they did.

    On one point, though, I still cannot reconcile my own thinking with Microsoft's design: how is it that a C# keyword ("using") is defined in terms of a data type written in C# ("IDisposable")?.

    Doesn't this make the definition of C# circular? The language definition depends on an interface definition, which, of course, depends on the language definition because it is written in the language, etc. My feeling is that there are two potential ways around this problem:

    1) Write the C# standards document in special order, i.e. start with the bulk of of the C# definition, follow up with the definition of "IDisposable" (which uses the parts of the language already defined), and then finish with the definition of "using"

    OR

    2) Define "using" as essentially a macro, i.e. something that gets dropped into one's code in simplistic fashion. In this case, the definition of "using" does not formally reference "IDisposable"... there just happens to be an interface of that name in the framework that works well with "using." I guess if this is the case, then "using" can actually be applied to types that do not technically implement "IDisposable," but simply happen to have the necessary "Dispose" member.

    It doesn't really matter which of these conjuring tricks Microsoft actually used in defining C#. My problem is that both options seem ghetto to me. More formally, I don't think either one has precedent in any computer language that is widely used. I would be interested in seeing a counterexample if anyone can come up with one.

    By "ghetto," I also mean that parameterized macros as would be necessary for #2 are typically frowned upon by architect types. Compile-time text replacement is tempting in its power, but ultimately gets to be very messy. What Microsoft wants is the neatness of IDisposable... but they can't actually build this into the language. They have to settle for coincidences, text replacement, etc.

    So, I still think this part of .NET, or of C# at least, is very out-of-place.


  • ♿ (Parody)

    The <FONT color=#000080>using</FONT> keyword is "syntactical sugar" (like a pre-compile macro) and is identical to a scoped try/finally block with a Dispose method called in finally.

    { Obj x = new Obj(); try { x.Work(); } finally { x.Dispose; } } 

    There are several instances of this, such as <FONT color=#000080>for each</FONT>, <FONT color=#000080>lock</FONT> and <FONT color=#000080>var</FONT>, where the compiler "unmacros" the code before compiling it. VisualBasic.NET is filled with even more examples of this, and properties kinda work that way, too.

    But to answer your question about C# being circular... it is not, because C# depends on the .NET framework (mscorlib), and mscorlib has nothing to do with C#. For all we know, mscorlib is written in C++ or MSIL; I guess it doesn't really matter what it's written in (C++ compilers can compile the code for themself)... but I think a lot of this goes back to the nature of managed code: at a minimum a "virtual machine" must exist (CLR in .NET, JVM in Java), and that virtual must be understood ("referenced") by an implementation language to be remotely effective.

    IOW, IDisposable is an interfaced defined in mscorlib, it's *not* a "data type written in C#.



  • @Alex Papadimoulis said:

    The <FONT color=#000080>using</FONT> keyword is "syntactical sugar" (like a pre-compile macro)...

    The problem I have with that is "using" is nevertheless shown in listings of C# keywords. I find this annoying, or at least atypical. The C and C++ language specifications (e.g. their keyword lists) don't include preprocessor macros. Now, there are certain macros in C and C++ which have become ubiquitous, but they're kept out of the language spec and I think there are good reasons for that.

    To throw such things into the spec itself seems incestuous, i.e. the people creating the language are the same as the people writing libraries in the language. I don't think this is good... it seems stifling and synthetic to me. 

    More practically, one result of this incestuous little design process is rapid creep in what's considered "official," e.g. the Framework is "official," all these weird little macros are "official," Scott Guthrie's MVC pet project is "official," etc. Learning/using C# ends up meaning I must really learn/use these things as well, and accept all of their various notions about library design. This isn't true of C / C++ or even Java. The closest analog I can think of is Boost, which is somehow quasi-official C++, and I guess I have a bit of a problem with that as well (although I do use Boost).

    @Alex Papadimoulis said:

    But to answer your question about C# being circular... it is not, because C# depends on the .NET framework (mscorlib), and mscorlib has nothing to do with C#.

    You're talking about the implementation. Most of what I wrote about in my original post involved the language itself. It can be implemented with hydraulics for all I care.

    I do agree that C# is not defined circularly, but I think Microsoft only achieves this by using something like #1 or #2 from my original post.

    @Alex Papadimoulis said:

    VisualBasic.NET is filled with even more examples of this, and properties kinda work that way, too.

    Yeah, I've been noticing that more and more as I deal with VB.NET code. It's weird to have members work at runtime even though they're not part of the static type of the instance variable (e.g. when no Intellisense is available for a call that ends up working at runtime). I still don't completely know to what extent this involves Reflection, "dynamic blocks," Variants, Deviants, etc... To some extent I've given up, or at least deferred, any attempt to fully understand what VB.NET is actually doing.



  • @bridget99 said:

    @Alex Papadimoulis said:

    The <font color="#000080">using</font> keyword is "syntactical sugar" (like a pre-compile macro)...

    The problem I have with that is "using" is nevertheless shown in listings of C# keywords. I find this annoying, or at least atypical. The C and C++ language specifications (e.g. their keyword lists) don't include preprocessor macros. Now, there are certain macros in C and C++ which have become ubiquitous, but they're kept out of the language spec and I think there are good reasons for that.

    Well excuse me, princess, but the C and C++ language specifications (ISO 9899 and 14882, respectively), [i]do[/i] include the definition of the standard library, and that includes macros. In C++ certain language features reference the library not unlike in C#, for example typeid's result is a std::type_info, and new can throw std::bad_alloc, and if an exception is thrown while another one is still flying, std::terminate is called.



  • @bridget99 said:

    The problem I have with that is "using" is nevertheless shown in listings of C# keywords. I find this annoying, or at least atypical.
     

     

     

    foreach is a keyword as well. It's basically a macro <and> it depends on the IEnumerable<T> interface.

     

    I suppose you're sort of trying to say that the using statement violates some "rule" whereby the language cannot depend on any specific type or structure already existing? (including built in framework components and interfaces). This makes sense in many ways- the language should avoid having structures that rely on implementations of interfaces or anything like that. But, truly speaking, how does that help speed development? How does that make it easier? the using() statement was conceived because there was a distinct patter emerging. foreach, as implemented in several languages, came about because there was a clear pattern emerging using loops to iterate through collections. Considering the interfaces being dealt with come as a sort of package deal with C# it seems silly to try to stick to some idyllic mantra of separating control flow and "macro" type structures completely from these always-present interfaces. 



  • @Spectre said:

    Well excuse me, princess, but the C and C++ language specifications (ISO 9899 and 14882, respectively), do include the definition of the standard library, and that includes macros. In C++ certain language features reference the library not unlike in C#, for example typeid's result is a std::type_info, and new can throw std::bad_alloc, and if an exception is thrown while another one is still flying, std::terminate is called.

    Ah. Touché. But I think the C and C++ keyword lists are at least free of such chicanery. Maybe my argument boils down to a fairly petty disagreement over whether "using" should be considered a keyword, or something else.



  • @Sceptre said:

    Well excuse me, princess, but the C and C++ language specifications (ISO 9899 and 14882, respectively), do include the definition of the standard library, and that includes macros. In C++ certain language features reference the library not unlike in C#, for example typeid's result is a std::type_info, and new can throw std::bad_alloc, and if an exception is thrown while another one is still flying, std::terminate is called.

    The Standard Library is more analogous to the .NET Framework than it is to the C# language. I've got no problem with macros and whatever else being declared in the Framework. I do agree that "typeid" is roughly similar to "using" in the way it's declared... perhaps I chose a poor example in mentioning C++. There is a fair amount of brain rot in C++ as well, and I don't really approve of "typeid" wholeheartedly either.

    @BC_Programmer said:

    foreach is a keyword as well. It's basically a macro <and> it depends on the IEnumerable<T> interface.

    Yeah, and I don't like that either. My original post was focused on memory reclamation so I focused on "using."

    @BC_Programmer said:

    I suppose you're sort of trying to say that the using statement violates some "rule" whereby the language cannot depend on any specific type or structure already existing? (including built in framework components and interfaces). This makes sense in many ways- the language should avoid having structures that rely on implementations of interfaces or anything like that. But, truly speaking, how does that help speed development? How does that make it easier? the using() statement was conceived because there was a distinct patter emerging. foreach, as implemented in several languages, came about because there was a clear pattern emerging using loops to iterate through collections. Considering the interfaces being dealt with come as a sort of package deal with C# it seems silly to try to stick to some idyllic mantra of separating control flow and "macro" type structures completely from these always-present interfaces. 

    I don't think .NET programmers are singing the praises of "using." In fact, I don't think many of them even know about it. I know about it, and I don't really use it. I call Close() on I/O objects like connections, streams, etc. If this needs to be in a "finally" block (probably not the case unless there's some sort of error recovery going on), then I put it in one. If I am using unmanaged resources like DCs or GDI objects I just call ReleaseDC(), DeleteObject(), etc. when I'm done. I don't use stateful classes to deal with my unmanaged resources; like most programmers I just grab code snippets off of PInvoke.net and use them inline or in static helpers.

    In other words, the problem "using" and "Dispose" were designed to solve basically does not exist in practice. There is, practically speaking, a category of resource (file, connection, etc.) that necessitates a "Close" call in pretty much any computer language. And in C#, I guess I can probably call Dispose() instead, or even employ a "using" block. Maybe that will make Scott Guthrie love me. But Microsoft has done us no favors with its half-hearted attempt to rename "Close()" to "Dispose()."

    Beyond that, a great deal of old knowledge - some of it dating back to Win16 - is necessary to do advanced .NET programming, e.g. to use PInvoke. The Microsoft answer ("just use a class and make damn sure you call Dispose()") is not being applied in practice.

    Even worse, all of this legacy baggage does not exempt us from the lumpy performance inherent to automatic garbage collection. Finally, this entire "non-deterministic garbage collection" scheme has been foisted upon us in the name of preventing one small category of error, i.e. heap leaks. Remember - even C has automatic garbage collection for stack variables. In C terms, these are even known as "automatic variables." And even C# doesn't automatically reclaim DCs, database connections, etc.

    Overall, it is a design I do not understand.



  • @bridget99 said:

    @Spectre said:

    Well excuse me, princess, but the C and C++ language specifications (ISO 9899 and 14882, respectively), do include the definition of the standard library, and that includes macros. In C++ certain language features reference the library not unlike in C#, for example typeid's result is a std::type_info, and new can throw std::bad_alloc, and if an exception is thrown while another one is still flying, std::terminate is called.

    Ah. Touché. But I think the C and C++ keyword lists are at least free of such chicanery. Maybe my argument boils down to a fairly petty disagreement over whether "using" should be considered a keyword, or something else.

    new and typeid are keywords, you know? ;=] And using should be considered a keyword, because, well, it is one. It's in the spec.

    By the way, why are you so focused on the using statement? There is quite a lot of C# language features depending on the library, like:

    • Everything is a subtype of System.Object, arrays are subtypes of System.Array.
    • System.NullReferenceException and System.IndexOutOfRangeException and others can be easily obtained without directly referring to them.
    • As mentioned above, the foreach statement depends on System.Collections.Generic.IEnumerable<T> and System.Collections.IEnumerable, and lock on System.Threading.Monitor. Likewise, the yield statement depends on System.Collections.IEnumerator and System.Collections.Generic.IEnumerator<T>.

    C# was designed to run on .NET, so it's hardly surprising it depends on the framework.



  • @bridget99 said:

    I don't think .NET programmers are singing the praises of "using." In fact, I don't think many of them even know about it. I know about it, and I don't really use it. I call Close() on I/O objects like connections, streams, etc. If this needs to be in a "finally" block (probably not the case unless there's some sort of error recovery going on), then I put it in one. If I am using unmanaged resources like DCs or GDI objects I just call ReleaseDC(), DeleteObject(), etc. when I'm done. I don't use stateful classes to deal with my unmanaged resources; like most programmers I just grab code snippets off of PInvoke.net and use them inline or in static helpers.

    In other words, the problem "using" and "Dispose" were designed to solve basically does not exist in practice. There is, practically speaking, a category of resource (file, connection, etc.) that necessitates a "Close" call in pretty much any computer language. And in C#, I guess I can probably call Dispose() instead, or even employ a "using" block. Maybe that will make Scott Guthrie love me. But Microsoft has done us no favors with its half-hearted attempt to rename "Close()" to "Dispose()." 

    That is your own experiance.  I find using extremely useful when I might have multiple return statements and I can just wrap them in a using block and it ensures that when the block is existed the Dispose() method gets called wether it exits by a return, a thrown exception, or just regular program flow. 

    @bridget99 said:

     

    Even worse, all of this legacy baggage does not exempt us from the lumpy performance inherent to automatic garbage collection. Finally, this entire "non-deterministic garbage collection" scheme has been foisted upon us in the name of preventing one small category of error, i.e. heap leaks. Remember - even C has automatic garbage collection for stack variables. In C terms, these are even known as "automatic variables." And even C# doesn't automatically reclaim DCs, database connections, etc.

    There are plenty of other errors caused by manual memory allocation

    1. Segmentation faults from doing any number of things

    2. Clobbering the stack by using a reference to a variable which was on the stack.  (BTW, have you ever considered the possibilty of the stack variables being used inside of a closure like a lambda expression?)

    3. tired fingers from all the extra coding required to do your own memory management.

     



  • Wha? Someone's hating on [code]using[/code] and garbage collection? Really?

    @the thread said:

    posted by bridget99

    Oh, it's you.

    @bridget99 said:

    If I am using unmanaged resources like DCs or GDI objects I just call ReleaseDC(), DeleteObject(), etc. when I'm done. I don't use stateful classes to deal with my unmanaged resources; like most programmers I just grab code snippets off of PInvoke.net and use them inline or in static helpers.

     I do. There's this concept called [i]exception safety[/i]. You may have heard about it once or twice while coding in C++. The same problem exists in .NET, except for memory (which was, like, 75% of the problem in C++). For resources that also have classes (eg, FileStream), they get a safety net in their finalizer. However, they also implement IDisposable in order to facilitate performance and correct behaviour.

    FileStream foo = //whatever
    foo.Write(mySourceOfData.GetBytes());
    foo.Close()

    What's wrong with this snippet? If GetBytes() can throw an exception, then you leak a file handle until some random time in the future ([url=http://blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx]maybe never[/url]), and any future attempts to open this file handle for writing will fail.

    So, let's add an exception handler.

    FileStream foo;
    try {
    foo = //whatever
    foo.Write(mySourceOfData.GetBytes());
    } finally {
        foo.Close();
    }

    Well, that more than doubled the LOC, and made me break up the variable declaration from the assignment in order to get the proper scoping. Plus, this very code will be used in many places with little to no modification. If only there was some way to fix these problems, while still having the same semantics...

    using(FileStream foo = /*whatever*/) {
    foo.Write(mySourceOfData.GetBytes());
    }

    Oh, hi, didn't see you there.

    Edit: Oh, I forgot one other thing. In C/++, there are two types of variables: pointers, and non-pointers. They reflect heap objects and stack objects, respectively.

    In C#, with the exception of ValueType objects (which are special), [i]every variable is a pointer, pointing to the heap[/i]. Every single one of them. The design of C# is not so much to prevent leaks (heck, leaking is easy to do anyway), but to prevent a much more dangerous class of errors: dangling, or invalid, pointers. Thanks to the garbage collector, we know that every single variable points to something meaningful, an impossible guarantee in C or C++.


Log in to reply