H
Helge Jensen
In C# 2.0 System.IO.Stream is declared as:
public class Stream: ..., IDisposable {
...
public void Dispose();
public void Dispose(bool);
IDisposable.Dispose();
}
Which must be a design-blunder, if not a 100-year sleep. It prevents
polymorphic disposing of Stream instances through ((Stream)s).Dispose().
Have a look at some ways to *try* to implement a Stream which needs a
custom dispose:
public class MyStream: Stream {
...
/* first, the natural, right and proper way: */
public override void Dispose(); // Dispose is non-virtual
/* bring on the hacks: */
public new Dispose(); // wrong semantics on ((Stream)s).Dispose()
void IDisposable.Dispose(); // using(Stream s = new MyStream...) ?!
}
The bottom-line is that Stream implementations with custom Dispose
*cannot* possibly behave correctly in *all* usage scenarios, and even
worse in the pretty-common scenario where the user knows the instance as
Stream by polymorphism and invokes Dispose yields *wrong* semantics,
*silently* using all possible implementations of MyStream.
To find out what happens in the "using" case, I turned to the ECMA
draft spec.
(http://download.microsoft.com/download/8/1/6/81682478-4018-48fe-9e5e-f87a44af3db9/standard.pdf,
p. 238). It is not really easy to read. To me ECMA sounds like it
declares "public void Dispose()" as "the magic protocol for using", with
"IDisposable.Dispose()" as a backup method. I understand that reasoning
for value-types, in order to actually mutate the value, instead of
disposing a Boxed copy of the value-type.
Writing a test-program which is found at the bottom of the post I
dreaded and predicted the output:
Bar.Dispose()
Bar.Dispose()
Foo.Dispose()
Foo.Dispose()
Baz.Dispose()
Baz.Dispose()
Since the resource-type "R", in this case, Foo and Bar respectively
declares a single public, void-returning, parameterless method named
Dispose.
But the actual output from vs2005 is (as I *hoped* ECMA would have
specified):
Bar.Dispose()
Bar.IDisposable.Dispose()
Foo.Dispose()
Bar.IDisposable.Dispose()
Baz.Dispose()
Baz.IDisposable.Dispose()
Which is almost good enough... only the virtual dispatch in
((Foo)bar).Dispose() is missing -- because of the non-virtual
Foo.Dispose blunder. Note that this explicitly *does* *not* invoke
Baz.Dispose() when using(baz).
I can't see (in my current anger anyway how a correct ECMA
implementation could produce the above output.
I see no reason to allow "using" of non-IDisposable reference-types, and
a hacky protocol through "public void Dispose()" for value-types. A
slightly less intrusive protocol would be to execute using without
boxing values as a special-case, limiting the special-cases to a
specific interface instead of contaminating the normal name-space for
methods.
<having a fit>
On top of *that*, Stream has now got a Dispose(bool). what good can that
possibly do? Stream.Dispose() is about *semantics* not implementation in
managed vs. unmanaged space.
What use is it to be able to *possibly* "release managed resources"
polymorphicly in a semantic statement declaring the Stream dead.
</having a fit>
OK, not that's off my chest I better go find the right place to report
this so it can get fixed.
====> test program <====
using System;
using System.IO;
class VirtualDisposeExample
{
public class Foo : IDisposable
{
public void Dispose()
{ Console.WriteLine("Foo.Dispose()"); }
void IDisposable.Dispose()
{ Console.WriteLine("Foo.IDisposable.Dispose()"); }
}
public class Bar : Foo, IDisposable
{
public new void Dispose() { Console.WriteLine("Bar.Dispose()"); }
void IDisposable.Dispose()
{ Console.WriteLine("Bar.IDisposable.Dispose()"); }
}
public struct Baz : IDisposable
{
public void Dispose()
{ Console.WriteLine("Baz.Dispose()"); }
void IDisposable.Dispose()
{ Console.WriteLine("Baz.IDisposable.Dispose()"); }
}
static void Main(string[] args)
{
Bar bar = new Bar();
bar.Dispose();
using (bar)
; // Dispose bar through using
Foo foo = bar;
foo.Dispose();
using (foo)
; // Dispose foo through using
Baz baz = new Baz();
baz.Dispose();
using (baz)
; // Dispose baz through using
}
}
public class Stream: ..., IDisposable {
...
public void Dispose();
public void Dispose(bool);
IDisposable.Dispose();
}
Which must be a design-blunder, if not a 100-year sleep. It prevents
polymorphic disposing of Stream instances through ((Stream)s).Dispose().
Have a look at some ways to *try* to implement a Stream which needs a
custom dispose:
public class MyStream: Stream {
...
/* first, the natural, right and proper way: */
public override void Dispose(); // Dispose is non-virtual
/* bring on the hacks: */
public new Dispose(); // wrong semantics on ((Stream)s).Dispose()
void IDisposable.Dispose(); // using(Stream s = new MyStream...) ?!
}
The bottom-line is that Stream implementations with custom Dispose
*cannot* possibly behave correctly in *all* usage scenarios, and even
worse in the pretty-common scenario where the user knows the instance as
Stream by polymorphism and invokes Dispose yields *wrong* semantics,
*silently* using all possible implementations of MyStream.
To find out what happens in the "using" case, I turned to the ECMA
draft spec.
(http://download.microsoft.com/download/8/1/6/81682478-4018-48fe-9e5e-f87a44af3db9/standard.pdf,
p. 238). It is not really easy to read. To me ECMA sounds like it
declares "public void Dispose()" as "the magic protocol for using", with
"IDisposable.Dispose()" as a backup method. I understand that reasoning
for value-types, in order to actually mutate the value, instead of
disposing a Boxed copy of the value-type.
Writing a test-program which is found at the bottom of the post I
dreaded and predicted the output:
Bar.Dispose()
Bar.Dispose()
Foo.Dispose()
Foo.Dispose()
Baz.Dispose()
Baz.Dispose()
Since the resource-type "R", in this case, Foo and Bar respectively
declares a single public, void-returning, parameterless method named
Dispose.
But the actual output from vs2005 is (as I *hoped* ECMA would have
specified):
Bar.Dispose()
Bar.IDisposable.Dispose()
Foo.Dispose()
Bar.IDisposable.Dispose()
Baz.Dispose()
Baz.IDisposable.Dispose()
Which is almost good enough... only the virtual dispatch in
((Foo)bar).Dispose() is missing -- because of the non-virtual
Foo.Dispose blunder. Note that this explicitly *does* *not* invoke
Baz.Dispose() when using(baz).
I can't see (in my current anger anyway how a correct ECMA
implementation could produce the above output.
I see no reason to allow "using" of non-IDisposable reference-types, and
a hacky protocol through "public void Dispose()" for value-types. A
slightly less intrusive protocol would be to execute using without
boxing values as a special-case, limiting the special-cases to a
specific interface instead of contaminating the normal name-space for
methods.
<having a fit>
On top of *that*, Stream has now got a Dispose(bool). what good can that
possibly do? Stream.Dispose() is about *semantics* not implementation in
managed vs. unmanaged space.
What use is it to be able to *possibly* "release managed resources"
polymorphicly in a semantic statement declaring the Stream dead.
</having a fit>
OK, not that's off my chest I better go find the right place to report
this so it can get fixed.
====> test program <====
using System;
using System.IO;
class VirtualDisposeExample
{
public class Foo : IDisposable
{
public void Dispose()
{ Console.WriteLine("Foo.Dispose()"); }
void IDisposable.Dispose()
{ Console.WriteLine("Foo.IDisposable.Dispose()"); }
}
public class Bar : Foo, IDisposable
{
public new void Dispose() { Console.WriteLine("Bar.Dispose()"); }
void IDisposable.Dispose()
{ Console.WriteLine("Bar.IDisposable.Dispose()"); }
}
public struct Baz : IDisposable
{
public void Dispose()
{ Console.WriteLine("Baz.Dispose()"); }
void IDisposable.Dispose()
{ Console.WriteLine("Baz.IDisposable.Dispose()"); }
}
static void Main(string[] args)
{
Bar bar = new Bar();
bar.Dispose();
using (bar)
; // Dispose bar through using
Foo foo = bar;
foo.Dispose();
using (foo)
; // Dispose foo through using
Baz baz = new Baz();
baz.Dispose();
using (baz)
; // Dispose baz through using
}
}