How to release unmanaged resources in .NET
# Global tip / Remarks
a] when developing using a managed platform such as .NET, DO remember that the garbage collector only regains MEMORY resources from managed objects. It is up to you to close and release other resources (COM/COM+ objects, files, network connection, DB connection /data reader, ...), the sooner the better.
b] Not all unmanaged resources can be released/free using the same method. Most of them implements the interface System.IDisposable but it is not mandatory, just good practice. Read the documentation to find out.
c] Usually a class which inherits from System.MarshalByRefObject will implements IDisposable or have some private mechanism to release unmanaged resources. This is a rule of thumb and is not always true, but enough to pay attention to it and read the documentation about the .NET class.
d] In order to use the C# construct "using" the type you want to instantiate must have a Dispose() method. It can do so by implementing the interface System.IDisposable but it is not mandatory.
# COM objects (.NET class exposed to COM or Legacy code exposed via COM, e.g. VB code)
- They tend to inherit from System.MarshalByRefObject, but is not mandatory.
- Examples of such things are when you use COM Interops in your .NET code.
- You should release the reference to a COM interface by using ReleaseComObject() as shown below:
BasketClass voucherBasket = null;
try
{
voucherBasket = new BasketClass();
... IF YOU NEED TO USE ANOTHER COM class, DO ANOTHER try/finally BLOCK INSIDE
... DO NOT SET voucherBasket to "null" IN THE try BLOCK, UNLESS IF CALL ReleaseComObject AS WELL
}
... catches if needed
finally
{
if (voucherBasket != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject( voucherBasket );
}
}
- Another variant of this is the following
BasketClass voucherBasket = null;
voucherBasket = new BasketClass(); // MAKE SURE YOU HAVE ANOTHER try/finally BLOCK TO CATCH ANY EXCEPTION DURING INSTANTIATION
try
{
... IF YOU NEED TO USE ANOTHER COM class, DO ANOTHER try/finally BLOCK INSIDE
}
... catches if needed
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject( voucherBasket );
}
# COM+ objects (.NET class exposed to COM+ by inheriting from ServicedComponent)
- All .NET classes which want to benefits from COM+ Enterprise Services must inherit from System.EnterpriseServices.ServicedComponent (which along the line implements IDisposable).
- You should use the "Dispose pattern".
using(Tesco.Common.Operations.CustomerOperations customerOperation = new Tesco.Common.Operations.CustomerOperations())
{
... DO WHATEVER YOU NEED TO DO ...
... IF YOU NEED TO USE ANOTHER SERVICEDCOMPONENT, DO ANOTHER using BLOCK INSIDE...
}
# COM+ objects (Legacy code exposed via COM+, e.g. VB code)
- You interop with such objects the same way as for COM objects (=> COM Interops).
- They do not implements IDisposable interface or a Dispose() method, so you cannot use the "Dispose pattern".
- you must use "System.Runtime.InteropServices.Marshal.ReleaseComObject" as shown above for COM objects.
# Summary about COM and COM+
Client | Server | Client calls Dispose() | Client calls ReleaseComObject() | Client calls IUnknown.Release() |
.NET | .NET class inheriting from ServicedComponent | Yes | - | - |
.NET | Legacy COM or Legacy COM+ | - | Yes | - |
Legacy (VB, ASP) | .NET class inheriting from ServicedComponent | Yes | - | Yes |
Legacy (VB, ASP) | .NET class exposed to COM | Yes If the .NET class implements a Dispose() method | - | Yes |
# System.IO.* (e.g. Stream, TextWriter, TextReader, ...) objects
- Usually those .NET classes implements IDisposable, so you should use the "Dispose pattern"
string message = null;
using (MemoryStream memoryStream = new MemoryStream())
{
using (StreamWriter streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.ASCII))
{
this.TransformData(xmldata, base.Xsls.ForgotPasswordEmail, null, streamWriter);
memoryStream.Position = 0;
using (StreamReader streamReader = new StreamReader(memoryStream, System.Text.Encoding.ASCII))
{
message = streamReader.ReadToEnd();
}
}
}
# Becareful with Properties/method/Collections returning COM/COM+ objects
- See this extract of code:
try
{
VoucherBasket voucherBasket = new VoucherBasket();
try
{
voucherBasket.Customer.GetCustomerId();
}
finally
{
ReleaseComObject(voucherBasket);
}
}
... catches if needed
finally
{
...
}
- The problem here is the "Get" property "" which returns under the cover a reference to a COM/COM+ object (i.e. a CCW, COM Callable Wrapper).
The object is not released until it gets collected by the garbage collector. When? we cannot be certain.
- Good way (assuming Customer is a legacy COM class):
try
{
VoucherBasket voucherBasket = new VoucherBasket();
try
{
Customer customer = voucherBasket.Customer;
try
{
customer.GetCustomerId();
}
finally
{
ReleaseComObject(customer);
}
}
finally
{
ReleaseComObject(voucherBasket);
}
}
... catches if needed
finally
{
...
}
# Further Readings
- Enterprise Services FAQ
- Programming for Garbage Collection
- .NET Framework Resource Management
No comments:
Post a Comment