A re-announce on GC's defects

B

Born

"> A solution (but you're not going to like it) would be to do it manually
via IDisposable. Create a proxy with the reference count and a reference
to the resource, and create handles with a reference to both the
resource and the proxy and that decrement the reference count when
disposed. Have the proxy dispose the resource when the refcount reaches
0. Only hand out handles to the resource, and (if necessary) wrap access
to the resource so that it's not visible to the outside. The handles
could have a Clone() method that creates a new handle and increments the
refcount.

That technique turns N-ownership into N instances of 1-ownership. So
theoretically, if you're able to deal with a single parent, you're able
to deal with N single parents (and thus N parents).

-- Barry


I thought about similiar things. That seems to be the only way at present if
I want to use C# to give a general purpose framework. I admit I don't like
it because inexperienced user or exception can easily ruin it.
 
B

Barry Kelly

Born said:
I have to say my charges are not answered.

What, are you prosecutor, judge *and* jury? Ha ha!
No practical solution but
criticisms. I was told C# is not fit for my need or I gave bad cases.

You criticise GC for not managing resources. It's like criticising cars
for being bad boats.

Object lifetime and resource lifetime are similar, so similar that other
languages (such as C++) put code managing them in the same place - the
destructor. However, GC is not a solution for both: it's a solution for
object lifetime *only*.

You've not responded to this point - it's the fundamental assumption on
which your argument falls flat.
As I said, what I want is a better world. The C++ can be better if it
integrates some new features.

You can use C++/CLI if you like, of course.

-- Barry
 
B

Born

Object lifetime and resource lifetime are similar, so similar that other
languages (such as C++) put code managing them in the same place - the
destructor. However, GC is not a solution for both: it's a solution for
object lifetime *only*.

You've not responded to this point - it's the fundamental assumption on
which your argument falls flat.

Well, I don't care if they are two different things or not. I want to use
object lifetime to tell its weak references that it's gone. It's a general
purpose solution. Why not?
 
B

Barry Kelly

Born said:
Well, I don't care if they are two different things or not.

I don't think you can use .NET effectively without respecting this
difference. It's too easy to leave files inadvertently locked, and have
that kind of failure - failure of resource acquisition of mutually
exclusive resources due to overlap.
I want to use
object lifetime to tell its weak references that it's gone. It's a general
purpose solution. Why not?

WeakReference can work for e.g. caches, but it's not really a callback
mechanism. For one thing, GC only occurs during allocation: depending on
the architecture, allocation may be a rare event. Especially if you've
architected the solution such that gen2 collections are rare, it may be
a long time before objects which have migrated to gen2 (and then died)
get collected.

WeakReference is for holding on to something that is somewhat expensive
to recreate, yet you want to give up under memory pressure; or
alternatively, for situations (such as weak delegates) when you want an
object to subscribe to callbacks or be similarly "contactable", but you
don't want those subscriptions themselves to keep the object alive.

-- Barry
 
B

Bruce Wood

Born said:
">

Open you mind. The memory allocation state is a general solution to inform
a weak reference that its target has gone. So why should I use domain
context to complicate things?

Domain context solves domain problems. Memory allocation and
reclamation are housekeeping. Mixing concerns, otherwise known as a
"hack," leads to woe, in my experience.

Your general point about needing to add higher-level context in order
to do resource management in C# was interesting. I was merely pointing
out that your example was terrible, that's all. I was not claiming that
a terrible example invalidates the original argument.
Be creative. The world can be better. Imagine C++ with enhanced features
like Reflection in C#. This is what I'm trying to prove here. Actually I'm
checking with C++/CLI, but I'm not sure how good it can be.

Sorry, it still sounds to me as though you're railing that it's far too
difficult to pound a nail into the wall with a screwdriver, and
claiming that the problem is that the screwdriver needs a heavier
handle with a flatter surface on it with which to drive nails. I claim
that you should, instead, use a hammer, or perhaps employ a screw
instead of the nail. I don't see anything particularly closed-minded or
uncreative in that advice.
 
B

Bruce Wood

Born said:
Well, I don't care if they are two different things or not. I want to use
object lifetime to tell its weak references that it's gone.

I don't know nearly as much as Barry on this subject, but I immediately
note a semantic problem here: what do you mean by "gone"?

In C++ you have total control over that concept, and C++ software is
usually designed so that all definitions of "gone" occur simultaneously
in the object's destructor. However, this is an artifact of the
language, not a truism.

Let's take your game, for example. An object representing an adversary
can be killed, "gone" from the point of view of the player, but still
be on a queue somewhere waiting for some post-death processing, so not
yet "gone" from the point of view of the game software. In C#, the
object can have any resources it's holding released, but still have
references to it, in other words, Disposed, or "gone" from the point of
view of being any longer usable. Hopefully, a few nanoseconds after
that the last reference to the object goes out of scope, so now the
object is "gone" from the point of view of the program, but may still
be taking up memory. Then, finally, the GC reclaims it, so it is now
"gone" from all points of view.

So... what do you mean by "gone"?
 
B

Born

First, "gone" means the object's members (methods and variables) are not
available any longer. If you want post-processing, you either employ some
callbacks or simply use an ID of the object, depends on how you post-process
it. The advantage of using the object lifetime to notify its weak references
it's gone is, it's a general purpose solution and you need no extra logics.
Those logics may eventually lead to IsKilled, IsDetroyed,IsVanished... in
practical projects lasting months with many people involved.

Second, I believe object lifetime and the lifetime of the resources the
object owns are the same thing. Why, this is what encapsulation means. An
object encapsulates its resources and their lifetimes are bound together.
 
B

Born

I've finished my research on C++/CLI.

With C++/CLI (VS2005), things turn out to be as I expected. Now I have stack
objects, delete, Reflection and so on that I believe can make the world
better.

Although reference objects with stack semantics are not really on stack, it
solves the problem I addressed. The only pity is that only C++ clients can
use this feature and not other .NET languages. However, I will not be
surprised if they extend it to C#,BASIC in the future.

Thank you guys, for without you, I could not have defined and solved the
problem within such a short time.
 
B

Barry Kelly

Born said:
Second, I believe object lifetime and the lifetime of the resources the
object owns are the same thing. Why, this is what encapsulation means. An
object encapsulates its resources and their lifetimes are bound together.

I understand why you say this - you're coming from a C++ perspective -
but you must understand that C++ is a different language from C#. What's
true for C++ is *not* necessarily true in the managed worlds of C# and
Java.

But don't take my word for it. Here are two annotations from the page I
linked earlier
(http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae):

"Annotation (Joe Duffy): Earlier in the .NET Framework’s lifetime,
finalizers were consistently referred to as destructors by C#
programmers. As we become smarter over time, we are trying to come to
terms with the fact that the Dispose method is really more equivalent to
a C++ destructor (deterministic), while the finalizer is something
entirely separate (nondeterministic). The fact that C# borrowed the C++
destructor syntax (i.e. ~T()) surely had at least a little to do with
the development of this misnomer. Confusing the two has been unhealthy
in general for the platform, and as we move forward the clear
distinction between resource and object lifetime needs to take firm root
in each and every managed software engineer’s head."

*** Note this especially, I'm repeating it to call it out:
"the clear distinction between resource and object lifetime needs to
take firm root in each and every managed software engineer’s head."

"Annotation (Jeffrey Richter): It is very unfortunate that the C# team
chose to use the tilde syntax to define what is now called a finalizer.
Programmers coming from an unmanaged C++ background naturally think that
you get deterministic cleanup when using this syntax. I wish the team
had chosen a symbol other than tilde; this would have helped developers
substantially in learning how the .NET platform is different than the
unmanaged architecture."

-- Barry
 
W

Willy Denoyette [MVP]

Born said:
I've finished my research on C++/CLI.

With C++/CLI (VS2005), things turn out to be as I expected. Now I have stack objects,
delete, Reflection and so on that I believe can make the world better.
Although reference objects with stack semantics are not really on stack, it solves the
problem I addressed. The only pity is that only C++ clients can use this feature and not
other .NET languages. However, I will not be surprised if they extend it to C#,BASIC in
the future.

IMO it's not gonna happen because it solves nothing that isn't solved with with the "using"
idiom. What they did in C++/CLI is provide "scope destructors", not deterministic behavior
per se. The syntax is also far less expressive than the "using" directive in C#, and you
have to put the variable in a separate scope, you can't declare it inline.
A user can still make mistakes, you can't force him to declare the variable holding a
reference with stack semantics, just like you can't force a user to call Dispose or apply
the "using" idiom. A reference that is not declared with stack semantics, still has to be
deleted to have deterministic destruction.

Willy.
 
C

Carl Daniel [VC++ MVP]

Willy said:
IMO it's not gonna happen because it solves nothing that isn't solved
with with the "using" idiom. What they did in C++/CLI is provide
"scope destructors", not deterministic behavior per se. The syntax is
also far less expressive than the "using" directive in C#, and you
have to put the variable in a separate scope, you can't declare it
inline.

While it's true that scope-based deterministic destruction solves the same
problem as the using statement in C#, it's a bit of a stretch to say that
it's "far less expressive". Perhaps you prefer to see

using (T1 t1 = new T1())
{
using (T2 t2 = new T2())
{
using (T3 t3 = new T3())
{
// some code
}
}
}

or even the short hand:

using (T1 t1 = new T1())
using (T2 t2 = new T2())
using (T3 t3 = new T3())
{
// some code
}

but it's hard for me to see how that's more expressive than

{
T1 t1;
T2 t2;
T3 t3;

// some code
}

Note that using variables also must be declared in their own scope since the
using statement itself defines a scope, so that argument is a non-starter..
A user can still make mistakes, you can't force him to declare the
variable holding a reference with stack semantics, just like you
can't force a user to call Dispose or apply the "using" idiom. A
reference that is not declared with stack semantics, still has to be
deleted to have deterministic destruction.

Unfortunately, that's true. In true C++ tradition, C++/CLI lets you hang
yourself with any number of different lengths of rope. At least it does
give a paradigm that's more comfortable to C++ programmers than the using
idiom.

-cd
 
W

Willy Denoyette [MVP]

Carl Daniel said:
While it's true that scope-based deterministic destruction solves the same problem as the
using statement in C#, it's a bit of a stretch to say that it's "far less expressive".
Perhaps you prefer to see
using (T1 t1 = new T1())
{
using (T2 t2 = new T2())
{
using (T3 t3 = new T3())
{
// some code
}
}
}

or even the short hand:

using (T1 t1 = new T1())
using (T2 t2 = new T2())
using (T3 t3 = new T3())
{
// some code
}

Honestly , I do, IMO it's clear that T1, T2 and T3 are disposable types. C# is a new
language and IMO the using idiom clearly states the intention of the author, I see no reason
(and so did Anders) why it should have adopted the C++ idiom.
but it's hard for me to see how that's more expressive than
{
T1 t1;
T2 t2;
T3 t3;

// some code
}
Here T1, T2 T3 can be anything, consider this...

// assembly xxx
namespace xyz
{
public struct T1 {
int i;
long v;
};
public struct T2 {
public:
T2() { xxx = new ...;}
...
~T2() { delete xxx;}
!T2(){}
};
...
...
}


// assembly yy
using namespace xyz;
{
T1 t1;
T2 t2;
// some code
}

Here for the "reader" (code maintenace) it's not that obvious that T2 refers to a
disposable type.
Note that using variables also must be declared in their own scope since the using
statement itself defines a scope, so that argument is a non-starter..

Not really, the variable can be declared at class scope or function scope

// class member or local
ExpensiveObject o;
....

using(o = new...)
{...}

Unfortunately, that's true. In true C++ tradition, C++/CLI lets you hang yourself with
any number of different lengths of rope. At least it does give a paradigm that's more
comfortable to C++ programmers than the using idiom.

Agreed, that's why it has been adopted by C++/CLI, but that doesn't mean all other managed
languages should have done the same (well they couldn't, C# pre-dates C++/CLI).


Willy.
 
A

Andre Kaufmann

Barry said:
Andre Kaufmann wrote:
A solution (but you're not going to like it) would be to do it manually
via IDisposable. Create a proxy with the reference count and a reference
to the resource, and create handles with a reference to both the
[...]

Thank you for posting this solution. I would love this solution - which
is similar to a smart pointer in C++ - if I could have the same
automatism like in C++ - RAII.

E.g. if I copy one smart pointer from one list to another, that the
reference counter is automatically incremented. With your solution at
least I could wrap the list with functions automatically doing this,
when adding a proxy. So at all I "like" this solution ;-) and think I
can live with it.

Perhaps I've been programming C++ too long and can't get used to another
programming style that easily ;-).
A C# developer coming from Delphi won't have such "problems".

Though C++ is quite complex and too has many downsides compared to C#(
no override, no sealed, slow compilation ...), I still prefer a more
complex language, which enables me to implement wrapper classes doing
just the right thing with my objects, so that I don't have to
permanently think about when to have to Dispose an object.

Reference counting doesn't scale that well and has too downsides. Though
something like reference counter based auto disposing would be very
good to have.

Andre
 
A

Andre Kaufmann

Willy said:
Born said:
I've finished my research on C++/CLI. [...]

IMO it's not gonna happen because it solves nothing that isn't solved
with with the "using" idiom. What they did in C++/CLI is provide "scope
destructors", not deterministic behavior per se.

Using is a perfect solution if you don't leave the function scope. But
if you have to leave the function scope, what shall be used then ?

Small example:

List<DisposableObject> list1; // Global
List<DisposableObject> list2; // lists


DisposableObject MyObject = new DisposableObject();

void f()
{
list1.Add(MyObject);
list2.Add(MyObject);
}

void r()
{
list1.Remove(MyObject);
}


Function r():
-------------
Now here I have the problem, how shall I know if
I have to call Dispose, without checking the other lists
if they still contain a reference to MyObject ?

In C++ I would use smart pointers, in C#
(The solution Barry has suggested - a proxy - could be used,
although in C++ reference counter are not easy to understand
but much easier to use)
[...]
Willy.

Andre
 
B

Barry Kelly

Andre said:
Thank you for posting this solution. I would love this solution - which
is similar to a smart pointer in C++ - if I could have the same
automatism like in C++ - RAII.

Perhaps I've been programming C++ too long and can't get used to another
programming style that easily ;-).
A C# developer coming from Delphi won't have such "problems".

Delphi Win32 users can also implement RAII, using interfaces. Interfaces
in Delphi are ref-counted COM-style interfaces, implemented behind the
scenes a lot like CComPtr<T> and friends.

-- Barry
 
A

Andre Kaufmann

Barry said:
Andre Kaufmann wrote:
[...]
Perhaps I've been programming C++ too long and can't get used to another
programming style that easily ;-).
A C# developer coming from Delphi won't have such "problems".

Delphi Win32 users can also implement RAII, using interfaces.

Semantically this is comparable to C++ RAII. Though there are downsides
using these interfaces, intentionally implemented to support COM, as a
RAII replacement.

At function scope it's IMHO too much overkill, since the interfaced
objects will be created on the native heap, instead being constructed on
the stack as in C++. This would be a rather slow approach, for releasing
resources at function scope commonly therefore try finally is used.

In .NET there isn't the same overhead as in Win32 in constructing
objects. However a stack based approach would be IMHO faster, but C# has
adopted the Delphi style and added the "using" keyword.
No big deal, though sometimes I'm missing RAII and assignment operator
overloading or some approach of automatic resource handling.

If you ignore the runtime overhead for object construction in
Delphi.W32, then I agree that you can use interfaces as a RAII replacement.
Interfaces
in Delphi are ref-counted COM-style interfaces, implemented behind the
scenes a lot like CComPtr<T> and friends.

Yes, but IMHO the internal implementation of interfaces is hidden too
much. They are perfect if you don't mix them with other objects, e.g.
hold them in the same object list. Otherwise I'll promise a developer
doing this having much fun ;-).

Andre
 
P

Philip Daniels

Willy said:
Born said:
I've finished my research on C++/CLI. [...]

IMO it's not gonna happen because it solves nothing that isn't solved
with with the "using" idiom. What they did in C++/CLI is provide "scope
destructors", not deterministic behavior per se.

Using is a perfect solution if you don't leave the function scope. But
if you have to leave the function scope, what shall be used then ?

Small example:

List<DisposableObject> list1; // Global
List<DisposableObject> list2; // lists


DisposableObject MyObject = new DisposableObject();

void f()
{
list1.Add(MyObject);
list2.Add(MyObject);
}

void r()
{
list1.Remove(MyObject);
}


Function r():
-------------
Now here I have the problem, how shall I know if
I have to call Dispose, without checking the other lists
if they still contain a reference to MyObject ?

In C++ I would use smart pointers, in C#
(The solution Barry has suggested - a proxy - could be used,
although in C++ reference counter are not easy to understand
but much easier to use)
[...]
Willy.

Andre

Agree. The prompt disposal of objects that live for longer than a
function is a problem. It would be really nice to have a new
attribute:

[ReferenceCount(true)]
class MyClass { ... }
 
A

Andre Kaufmann

Philip said:
[...]
Agree. The prompt disposal of objects that live for longer than a
function is a problem. It would be really nice to have a new
attribute:

[ReferenceCount(true)]
class MyClass { ... }

Yes would be nice. It's nice that .NET manages memory for me, though
regarding resources I'm missing some kind of automatism.


Andre
 
J

Jon Skeet [C# MVP]

<Philip Daniels <Philip Daniels>> wrote:

Agree. The prompt disposal of objects that live for longer than a
function is a problem. It would be really nice to have a new
attribute:

[ReferenceCount(true)]
class MyClass { ... }

The CLR team investigated all manner of solutions fairly exhaustively.
Because you could do something like:

object o = instanceOfMyClass;
object x = y;

etc, *every* assignment that could *possibly* be a reference-counted
type would need to check whether or not reference counting was
required. That's a painful performance hit.
 
A

Andre Kaufmann

Jon said:
<Philip Daniels <Philip Daniels>> wrote:

Agree. The prompt disposal of objects that live for longer than a
function is a problem. It would be really nice to have a new
attribute:

[ReferenceCount(true)]
class MyClass { ... }

The CLR team investigated all manner of solutions fairly exhaustively.
Because you could do something like:

object o = instanceOfMyClass;
object x = y;

But why should this assignment increase the reference counter ?
That's up to a smart pointer.

E.g.:

RefCountedObject obj1;
SmartPointer mySmartPointer1 = obj1; // Increase
SmartPointer mySmartPointer2 = mySmartPointer1; // Increase

RefCountedObject obj2 = obj1; // !!! Assignment

etc, *every* assignment that could *possibly* be a reference-counted
type would need to check whether or not reference counting was
required. That's a painful performance hit.

If you don't use another abstraction, like smart pointers, yes.

Smart pointers could IMHO be checked at compile time, otherwise they
would be painfully slow in C++ too. The only downside will be, that
Interlocked function must (should) be used to ensure thread safety,
which doesn't scale that well in multi threaded applications running on
dual core CPUs. But SmartPointers shouldn't be used as a general
replacement, only where they are needed. And then in most cases you will
have simpler and perhaps even faster code.

Andre
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top