开发者

Why is 'using' improving C# performances

开发者 https://www.devze.com 2023-01-04 00:35 出处:网络
It seems that in most cases the C# compiler could call Dispose() automatically. Like most cases of the using pattern look like:

It seems that in most cases the C# compiler could call Dispose() automatically. Like most cases of the using pattern look like:

public void SomeMethod()
{
    ...

    using (var foo = new Foo())
    {
        ...
    }

    // Foo isn't use after here (obviously).
    ...
}

Since foo isn't used (that's a very simple detection) and since its not provided as argument to another method (that's a supposition that applies to many use cases and can be extended), the compiler could automatically and immediately call Dispose() without the developper requiring to do it.

This means that in most cases the using is pretty useless i开发者_Go百科f the compiler does some smart job. IDisposable seem low level enough to me to be taken in account by a compiler.

Now why isn't this done? Wouldn't that improve the performances (if the developpers are... dirty).


A couple of points:

Calling Dispose does not increase performance. IDisposable is designed for scenarios where you are using limited and/or unmanaged resources that cannot be accounted for by the runtime.

There is no clear and obvious mechanism as to how the compiler could treat IDisposable objects in the code. What makes it a candidate for being disposed of automatically and what doesn't? If the instance is (or could) be exposed outside of the method? There's nothing to say that just because I pass an object to another function or class that I want it to be usable beyond the scope of the method

Consider, for example, a factory patter that takes a Stream and deserializes an instance of a class.

public class Foo
{
    public static Foo FromStream(System.IO.Stream stream) { ... }
}

And I call it:

Stream stream = new FileStream(path);

Foo foo = Foo.FromStream(stream);

Now, I may or may not want that Stream to be disposed of when the method exits. If Foo's factory reads all of the necessary data from the Stream and no longer needs it, then I would want it to be disposed of. If the Foo object has to hold on to the stream and use it over its lifetime, then I wouldn't want it to be disposed of.

Likewise, what about instances that are retrieved from something other than a constructor, like Control.CreateGraphics(). These instances could exist outside of the code, so the compiler wouldn't dispose of them automatically.

Giving the user control (and providing an idiom like the using block) makes the user's intention clear and makes it much easier to spot places where IDisposable instances are not being properly disposed of. If the compiler were to automatically dispose of some instances, then debugging would be that much more difficult as the developer had to decipher how the automatic disposal rules applied to each and every block of code that used an IDisposable object.

In the end, there are two reasons (by convention) for implementing IDisposable on a type.

  1. You are using an unmanaged resource (meaning you're making a P/Invoke call that returns something like a handle that must be released by a different P/Invoke call)
  2. Your type has instances of IDisposable that should be disposed of when this object's lifetime is over.

In the first case, all such types are supposed to implement a finalizer that calls Dispose and releases all unmanaged resources if the developer fails to do so (this is to prevent memory and handle leaks).


Garbage Collection (while not directly related to IDisposable, is what cleans up unused objects) isn't that simple.

Let me re-word this a little bit. Automatically calling Dispose() isn't that simple. It also won't directly increase performance. More on that a little later.

If you had the following code:

public void DoSomeWork(SqlCommand command)
{
    SqlConnection conn = new SqlConnection(connString);

    conn.Open();

    command.Connection = conn;

    // Rest of the work here
 }

How would the compiler know when you were done using the conn object? Or if you passed a reference to some other method that was holding on to it?

Explicitly calling Dispose() or using a using block clearly states your intent and forces things to get cleaned up properly.

Now, back to performance. Simply calling Dispose() on an Object doesn't guarantee any performance increase. The Dispose() method is used for "cleaning up" resources when you're done with an Object.

The performance increase can come when using un-managed resources. If a managed object doesn't properly dispose of its un-managed resources, then you have a memory leak. Ugly stuff.

Leaving the determination to call Dispose() up to the compiler would take away that level of clarity and make debugging memory leaks caused by un-managed resources that much more difficult.


You're asking the compiler to perform a semantic analysis of your code. The fact that something isn't explicitly referenced after a certain point in the source does not mean that it isn't being used. If I create a chain of references and pass one out to a method, which may or may not store that reference in a property or some other persistent container, should I really expect the compiler to trace through all of that and figure out what I really meant?

Volatile entities may also be a concern.

Besides, using() {....} is more readable and intuitive, which is worth a lot in terms of maintainability.

As engineers or programmers, we strive to be efficient, but that is rarely the same thing as lazy.


Look at the MSDN Artilce for the C# Using Statement The using statement is just a short cut to keep from doing a try and finally in allot of places. Calling the dispose is not a low level functionality like Garbage Collection.

As you can see using is translated into.

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

How would the compiler know where to put the finally block? Does it call it on Garbage Collection?

Garabage Collection doesn't happen as soon as you leave a method. Read this article on Garbage Collection to understand it better. Only after there are no references to the object. A resource could be tied up for much longer than needed.

The thought that keeps popping into my head is that the compiler should not protect developers who do not clean up there resources. Just because a language is managed doesn't mean that it is going to protect from yourself.


C++ supports this; they call it "stack semantics for reference types". I support adding this to C#, but it will require different syntax (changing the semantics based on whether or not a local variable is passed to another method isn't a good idea).


I think that you are thinking about finalizers. Finalizers use the destructor syntax in c#, and they are called automatically by the garbage collector. Finalizers are only appropriate to use when you are cleaning up unmanaged resources.

Dispose is intended to allow for early cleanup of unmanaged resources (and it can be used to clean managed resources as well).

Detection is actually trickier than it looks. What if you have code like this:


  var mydisposable = new...
  AMethod(mydisposable);
  // (not used again)

It's possible that some code in AMethod holds on to a reference to myDisposable.

  • Maybe it gets assigned to an instance variable inside of that method

  • Maybe myDisposable subscribes to an event inside of AMethod (then the event publisher holds a reference to myDisposable)

  • Maybe another thread is spawned by AMethod

  • Maybe mydisposable becomes "enclosed" by an anonymous method or lamba expression inside of AMethod.

All of those things make it difficult to know for absolute certain that your object is no longer in use, so Dispose is there to let a developer say "ok, I know that it's safe to run my cleanup code now);

Bear in mind also that dispose doesn't deallocate your object -- only the GC can do that. (The GC does have the magic to understand all of the scenarios that I described, and it knows when to clean up your object, and if you really need code to run when the GC detects no references, you can use a finalizer). Be careful with finalizers, though -- they are only for unmanaged allocations that your class owns.

You can read more about this stuff here: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx and here: http://www.bluebytesoftware.com/blog/2005/04/08/DGUpdateDisposeFinalizationAndResourceManagement.aspx

If you need unmanaged handle cleanup, read about SafeHandles as well.


It's not the responsibility of the compiler to interpret the scopes in your application and do things like figure out when you no longer need memory. In fact, I'm pretty sure that's an impossible problem to solve, because there's no way for the compiler to know what your program will look like at runtime, no matter how smart it is.

This is why we have the garbage collection. The problem with garbage collection is that it runs on an indeterminate interval, and typically if an object implements IDisposable, the reason is because you want the ability to dispose of it immediately. Like, right now immediately. Constructs such as database connections aren't just disposable because they have some special work to do when they get trashed - it's also because they are scarce.


I seems difficult for the G.C. to know that you won't be using this variable anymore later in the same method. Obviously, if you leave the method, and don't keep a further reference to you variable, the G.C. will dispose it. But using using in you sample, tells the G.C. that you are sure that you will not be using this variable anymore after.


The using statement has nothing to do with performance (unless you consider avoiding resource/memory leaks as performance).

All it does for you is guarantee that the IDisposable.Dispose method is called on the object in question when it goes out of scope, even if an exception has occurred inside the using block.

The Dispose() method is then responsible for releasing any resources used by the object. These are most often unmanaged resources such as files, fonts, images etc, but could also be simple "clean-up" activities on managed objects (not garbage collection however).

Of course if the Dispose() method is implemented badly, the using statement provides zero benefit.


I think the OP is saying "why bother with 'using' when the compiler should be able to work it out magically pretty easily".

I think the OP is saying that

public void SomeMethod() 
{ 
    ... 

    var foo = new Foo();

    ... do stuff with Foo ... 


    // Foo isn't use after here (obviously). 
    ... 
} 

should be equivalent to

public void SomeMethod() 
{ 
    ... 

    using (var foo = new Foo())
    {
    ... do stuff with Foo ... 
    }

    // Foo isn't use after here (obviously). 
    ... 
} 

because Foo isn't used again.

The answer of course is that the compiler cannot work it out pretty easily. Garbage Collection (what magically calls "Dispose()" in .NET) is a very complicated field. Just because the symbol isn't being used below that doesn't mean that the variable isn't being used.

Take this example:

public void SomeMethod() 
{ 
    ... 

    var foo = new Foo();
    foo.DoStuffWith(someRandomObject);
    someOtherClass.Method(foo);

    // Foo isn't use after here (obviously).
    // Or is it?? 
    ... 
} 

In this example, someRandomObject and someOtherClass might both have references to what Foo points out, so if we called Foo.Dispose() it would break them. You say you're just imagining the simple case, but the only 'simple case' where what you're proposing works is the case where you make no method calls from Foo and do not pass Foo or any of its members to anything else - effectively when you don't even use Foo at all in which case you probably have no need to declare it. Even then, you can never be sure that some kind of reflection or event hackery didn't get a reference to Foo just by its very creation, or that Foo didn't hook itself up with something else during its constructor.


In addition to the fine reasons listed above, since the problem can't be solved reliably for all cases, those "easy" cases are something that code analysis tools can and do detect. Let the compiler do stuff deterministically, and let your automatic code analysis tools tell you when you're doing something silly like forgetting to call Dispose.

0

精彩评论

暂无评论...
验证码 换一张
取 消