Reputation: 10015
I have a class which is is a simple wrapper for WNetUseConnection
Here is an implementation (just for reference):
internal class RemoteFileSystemContext : IDisposable
{
private readonly string _remoteUnc;
private bool _isConnected;
public RemoteFileSystemContext(string remoteUnc, string username, string password, bool promptUser)
{
if (WindowsNetworking.TryConnectToRemote(remoteUnc, username, password, promptUser))
{
_isConnected = true;
_remoteUnc = remoteUnc;
}
else
{
GC.SuppressFinalize(this);
}
}
public void Dispose()
{
Dispose(true);
}
~RemoteFileSystemContext()
{
Dispose(false);
}
private void Dispose(bool isDisposing)
{
if (!_isConnected)
return;
_isConnected = false;
if (isDisposing)
{
GC.SuppressFinalize(this);
}
WindowsNetworking.DisconnectRemote(_remoteUnc);
}
}
and here is usage:
using (var context = WindowsNetworking.CreateRemoteContext(storagePath, login, pass))
{
// do something with storagePath
GC.KeepAlive(context);
}
The question is if I should write GC.KeepAlive(context)
or not? I mean I didn't write code like this until I read an article (about AsyncLock
, but now I can't find a link), and now I'm not sure if GC can call a finalizer before this method finishes. Theoretically, it should use Dispose
in finally
section of using
, but this article was written by a smart guy, so I'm not sure now.
Just in case, I provide code for referenced class:
public static class WindowsNetworking
{
public static bool TryConnectToRemote(string remoteUnc, string username, string password, bool promptUser = false)
{
bool isUnc = remoteUnc != null && remoteUnc.Length >= 2 && remoteUnc[0] == '\\' && remoteUnc[1] == '\\';
if (!isUnc)
{
return false;
}
ConnectToRemote(remoteUnc, username, password, promptUser);
return true;
}
public static IDisposable CreateRemoteContext(string remoteUnc, string username, string password, bool promptUser = false)
{
return new RemoteFileSystemContext(remoteUnc, username, password, promptUser);
}
public static void DisconnectRemote(string remoteUNC)
{
var ret = (NetworkError) WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
if (ret != NetworkError.NO_ERROR)
{
throw new Win32Exception((int) ret, ret.ToString());
}
}
[DllImport("Mpr.dll")]
private static extern int WNetUseConnection(
IntPtr hwndOwner,
NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
int dwFlags,
string lpAccessName,
string lpBufferSize,
string lpResult
);
[DllImport("Mpr.dll")]
private static extern int WNetCancelConnection2(
string lpName,
int dwFlags,
bool fForce
);
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public int dwScope = 0;
public int dwType = 0;
public int dwDisplayType = 0;
public int dwUsage = 0;
public string lpLocalName = "";
public string lpRemoteName = "";
public string lpComment = "";
public string lpProvider = "";
}
private static void ConnectToRemote(string remoteUNC, string username, string password, bool promptUser)
{
NETRESOURCE nr = new NETRESOURCE
{
dwType = RESOURCETYPE_DISK,
lpRemoteName = remoteUNC
};
NetworkError ret;
if (promptUser)
ret = (NetworkError) WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
else
ret = (NetworkError) WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
if (ret != NetworkError.NO_ERROR)
{
throw new Win32Exception((int) ret, ret.ToString());
}
}
}
Upvotes: 4
Views: 767
Reputation: 127553
It is pretty easy to test, here is a quick test program, be sure it is run in release mode without a debugger attached.
using System;
namespace SandboxConsole
{
class Program
{
static void Main(string[] args)
{
using (var context = new TestClass())
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("After collection");
}
Console.WriteLine("After dispose, before 2nd collection");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("After 2nd collection");
Console.ReadLine();
}
}
internal class TestClass : IDisposable
{
public void Dispose()
{
Dispose(true);
}
~TestClass()
{
Console.WriteLine("In finalizer");
Dispose(false);
}
private void Dispose(bool isDisposing)
{
Console.WriteLine("In Dispose: {0}", isDisposing);
if (isDisposing)
{
//uncomment this line out to have the finalizer never run
//GC.SuppressFinalize(this);
}
}
}
}
It will always output
After collection In Dispose: True After dispose, before 2nd collection In finalizer In Dispose: False After 2nd collection
For more concrete proof, here is the IL for the above program's Main method
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 85 (0x55)
.maxstack 1
.locals init ([0] class SandboxConsole.TestClass context)
IL_0000: newobj instance void SandboxConsole.TestClass::.ctor()
IL_0005: stloc.0
.try
{
IL_0006: call void [mscorlib]System.GC::Collect()
IL_000b: call void [mscorlib]System.GC::WaitForPendingFinalizers()
IL_0010: call void [mscorlib]System.GC::Collect()
IL_0015: ldstr "After collection"
IL_001a: call void [mscorlib]System.Console::WriteLine(string)
IL_001f: leave.s IL_002b
} // end .try
finally
{
IL_0021: ldloc.0
IL_0022: brfalse.s IL_002a
IL_0024: ldloc.0
IL_0025: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_002a: endfinally
} // end handler
IL_002b: ldstr "After dispose, before 2nd collection"
IL_0030: call void [mscorlib]System.Console::WriteLine(string)
IL_0035: call void [mscorlib]System.GC::Collect()
IL_003a: call void [mscorlib]System.GC::WaitForPendingFinalizers()
IL_003f: call void [mscorlib]System.GC::Collect()
IL_0044: ldstr "After 2nd collection"
IL_0049: call void [mscorlib]System.Console::WriteLine(string)
IL_004e: call string [mscorlib]System.Console::ReadLine()
IL_0053: pop
IL_0054: ret
} // end of method Program::Main
You can see there is a hidden finally block that checks if the object is null then calls Dispose on it. That reference will keep the object alive the entire scope of the using block.
UPDATE: See Damien's comment below, this specific example does have the opportunity to actually call the finalizer early due to the fact I don't ever use any variables that use a implicit this
in the dispose method. To guarantee the behavior be sure to use a instance level variable (which my short example has none) or have GC.SuppressFinalize(this);
uncommented.
Upvotes: 1
Reputation: 239664
The GC.KeepAlive
method is empty. All it does is ensure that a particular variable is read from at that point in the code, because otherwise that variable is never read from again and is thus not a valid reference to keep an object alive.
It's pointless here because the same variable that you're passing to KeepAlive
is read from again at a later point in time - during the hidden finally
block when Dispose
is called. So, the GC.KeepAlive
achieves nothing here.
Upvotes: 4