Thursday, August 11, 2005

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

- .NET Framework Resource Management



No comments: