This doesn't make a lot of sense for me, since this is an IDisposable object shouldn't the GC also invoke the Dispose() method of the object?
The Dispose pattern, also known as IDisposable, provides two ways for an unmanaged object to be cleaned up. The Dispose method provides a direct and fast way to clean up the resource. The finalize method, which is called by the garbage collector, is a fail-safe way to make sure that the unmanaged resource is cleaned up in case another developer using the code forgets to call the Dispose method. This is somewhat similar to C++ developers forgetting to call Delete on heap allocated memory - which results in memory leaks.
According to the referenced link:
"Although finalizers are effective in some cleanup scenarios, they have two significant drawbacks:
The finalizer is called when the GC detects that an object is eligible for collection. This happens at some undetermined period of time after the resource is not needed anymore. The delay between when the developer could or would like to release the resource and the time when the resource is actually released by the finalizer might be unacceptable in programs that acquire many scarce resources (resources that can be easily exhausted) or in cases in which resources are costly to keep in use (e.g., large unmanaged memory buffers).
When the CLR needs to call a finalizer, it must postpone collection of the object’s memory until the next round of garbage collection (the finalizers run between collections). This means that the object’s memory (and all objects it refers to) will not be released for a longer period of time."
Using this method, the sockets never go into a TIME_WAIT state. Same if I just close the app before manually invoking Close() or Dispose()
Can someone shed some light and explain whether this would be a good practice (which I imagine people are going to say it's not).
The reason why it is taking a while for it shut down is because the code lingers by default to give the app some time to handle any queued messages. According to the TcpClient.Close method doc on MSDN:
"The Close method marks the instance as disposed and requests that the associated Socket close the TCP connection. Based on the LingerState property, the TCP connection may stay open for some time after the Close method is called when data remains to be sent. There is no notification provided when the underlying connection has completed closing.
Calling this method will eventually result in the close of the associated Socket and will also close the associated NetworkStream that is used to send and receive data if one was created."
This timeout value can be reduced or completely eliminated by the following code:
// Allow 1 second to process queued msgs before closing the socket.
LingerOption lingerOption = new LingerOption (true, 1);
tcpClient.LingerState = lingerOption;
tcpClient.Close();
// Close the socket right away without lingering.
LingerOption lingerOption = new LingerOption (true, 0);
tcpClient.LingerState = lingerOption;
tcpClient.Close();
Also interested in finding out whether it would be good practice to use this method to prevent TIME_WAIT states and ultimately whether this is a bug somewhere (i.e., should all sockets go through a TIME_WAIT state?)
As for setting the reference to the TcpClient object to null, the recommended approach is to call the Close method. When the reference is set to null, the GC ends up calling the finalize method. The finalize method eventually calls the Dispose method in order to consolidate the code for cleaning up the unmanaged resource. So, it will work to close the socket - its just not recommended.
In my opinion, it depends on the app whether or not some linger time should be allowed to give the app time to handle queued messages. If I was certain my client app had processed all the necessary messages, then I would probably either give it a linger time of 0 seconds or perhaps 1 second if I thought that might change in the future.
For a very busy client and / or weak hardware - then I might give it more time. For a server, I would have to benchmark different values under load.
Other useful references:
What is the proper way of closing and cleaning up a Socket connection?
Are there any cases when TcpClient.Close or Socket.Close(0) could block my code?