From AS3 to C#, Part 18: Resource Allocation and Cleanup
Today we continue the series by looking at how resources—primarily memory—are acquired and cleaned up in C#. We’ll go way beyond the new
operator and discuss advanced features like finalizers and using
blocks that can make releasing resources much less prone to errors. Read on to learn!
Table of Contents
- From AS3 to C#, Part 1: Class Basics
- From AS3 to C#, Part 2: Extending Classes and Implementing Interfaces
- From AS3 to C#, Part 3: AS3 Class Parity
- From AS3 to C#, Part 4: Abstract Classes and Functions
- From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks
- From AS3 to C#, Part 6: Extension Methods and Virtual Functions
- From AS3 to C#, Part 7: Special Functions
- From AS3 to C#, Part 8: More Special Functions
- From AS3 to C#, Part 9: Even More Special Functions
- From AS3 to C#, Part 10: Alternatives to Classes
- From AS3 to C#, Part 11: Generic Classes, Interfaces, Methods, and Delegates
- From AS3 to C#, Part 12: Generics Wrapup and Annotations
- From AS3 to C#, Part 13: Where Everything Goes
- From AS3 to C#, Part 14: Built-in Types and Variables
- From AS3 to C#, Part 15: Loops, Casts, and Operators
- From AS3 to C#, Part 16: Lambdas and Delegates
- From AS3 to C#, Part 17: Conditionals, Exceptions, and Iterators
- From AS3 to C#, Part 18: Resource Allocation and Cleanup
- From AS3 to C#, Part 19: SQL-Style Queries With LINQ
- From AS3 to C#, Part 20: Preprocessor Directives
- From AS3 to C#, Part 21: Unsafe Code
- From AS3 to C#, Part 22: Multi-Threading and Miscellany
- From AS3 to C#, Part 23: Conclusion
Let’s start off simply with the new
operator. It does the same thing in C# as it does in AS3: allocates memory for an instance of a class/struct and calls its constructor. It is by far the most common way to allocate resources in both languages.
// AS3 var p:Point = new Point();
// C# Point p = new Point();
AS3 has the Array
and Vector
classes for when you want to allocate a bunch of objects held in a container object. Array
holds its elements in two parts: a densely-packed part of contiguous elements and a sparsely-packed map/dictionary of elements. Vector
only has a densely-packed part. It’s somewhat strongly-typed, but there are many exceptions due to it being implemented behind the scenes by four different classes. Here’s how the two look:
var aVals:Array = new Array(); // empty array var aVals:Array = new Array(5); // array with 5 elements var aVals:Array = []; // empty array var aVals:Array = [1, 2, 3, 4, 5]; // array with these 5 elements var vVals:Vector.<int> = new Vector.<int>(); // empty vector var vVals:Vector.<int> = new Vector.<int>(5); // vector with 5 default elements var vVals:Vector.<int> = new <int>[]; // empty vector var vVals:Vector.<int> = new <int>[1, 2, 3, 4, 5]; // vector with these 5 elements var val:int = aVals[3]; // get fourth array element var val:int = vVals[3]; // get fourth vector element
C#, on the other hand, has strongly-typed arrays of densely-packed values, like AS3’s Vector
. You use the square brackets syntax ([]
) to refer to an array of something and the curly braces ({}
) syntax to initialize them.
int[] vals = new int[0]; // empty array int[] vals = new int[5]; // array with 5 default elements int[] vals = new int[]{}; // empty array int[] vals = new int[]{1, 2, 3, 4, 5}; // array with these 5 elements (alternate) int[] vals = {1, 2, 3, 4, 5}; // array with these 5 elements (alternate) int val = vals[3]; // get fourth array element
C# also explicitly supports the concept of multi-dimensional arrays like you’d use for tables or grids. You simply add a comma for each extra dimension:
int[,] vals = new int[2,3]; // 2x3 array of default elements int[,] vals = new int[2,3]{ {1,2,3}, {4,5,6} }; // 2x3 array of these elements int[,] vals = new int[,]{ {1,2,3}, {4,5,6} }; // 2x3 array of these elements int[,] vals = { {1,2,3}, {4,5,6} }; // 2x3 array of these elements int[,,] vals = new int[2,3,4]; // 2x3x4 array of default elements // 2x3x4 array of these elements int[,,] vals = new int[2,3,4]{ { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }, { {13,14,15,16}, {17,18,19,20}, {21,22,23,24} } }; // 2x3x4 array of these elements (alternate) int[,,] vals = new int[,,]{ { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }, { {13,14,15,16}, {17,18,19,20}, {21,22,23,24} } }; // 2x3x4 array of these elements (alternate) int[,,] vals = { { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }, { {13,14,15,16}, {17,18,19,20}, {21,22,23,24} } }; int val = vals[1,2]; // get array element at (1,2) int val = vals[1,2,3]; // get array element at (1,2,3)
This is in contrast to AS3’s approach to multi-dimensional arrays which is to nest arrays/vectors within arrays/vectors:
// 2x3 array of default elements var vals:Vector.<Vector.<int>> = new <int>[new Vector.<int>(3), new Vector.<int>(3)]; // 2x3 array of these elements var vals:Vector.<Vector.<int>> = new <int>[ new <int>[1, 2, 3], new <int>[4, 5, 6] ]; // 2x3x4 array of default elements var vals:Vector.<Vector.<Vector.<int>>> = new <int>[ new <int>[ new <int>[ new Vector.<int>(4) ], new <int>[ new Vector.<int>(4) ], new <int>[ new Vector.<int>(4) ] ], new <int>[ new <int>[ new Vector.<int>(4) ], new <int>[ new Vector.<int>(4) ], new <int>[ new Vector.<int>(4) ] ] ]; // 2x3x4 array of these elements var vals:Vector.<Vector.<Vector.<int>>> = new <int>[ new <int>[ new <int>[ new <int>[1, 2, 3, 4] ], new <int>[ new <int>(5, 6, 7, 8) ], new <int>[ new <int>(9, 10, 11, 12) ] ], new <int>[ new <int>[ new <int>(13, 14, 15, 16) ], new <int>[ new <int>(17, 18, 19, 20) ], new <int>[ new <int>(21, 22, 23, 24) ] ] ]; var val:int = vals[1][2]; // get array element at (1,2) var val:int = vals[1][2][3]; // get array element at (1,2,3) // (Array version omitted for brevity)
The AS3 version involves a lot of nested objects, each individually allocated and garbage-collected. Indexing into multi-dimensional arrays/vectors involves an index operation for each level of the array. This means there are three lookups for the 2x3x4 array above. In contrast, C#’s multi-dimensional arrays are a single block of memory that happens to know its dimensions. Querying them always involves one lookup, regardless of how many dimensions there are. The address of vals[1,2]
in a 2×3 array is the same as a one-dimensional array’s val[1*2 + 1*3]
or val[5]
. Essentially, expensive lookups are replaced with cheap multiplication and addition.
With resource allocation out of the way, let’s move on to the releasing those resources. Back in part five of this series we talked about destructors, also known as finalizers. Here’s how they look:
class File { public File(string path) { // ... open the file } // Destructor: ~File() { // ... close the file } }
The destructor is called by the garbage collector when it collects instances of the class. They’re a good way to clean up resources like file handles and database connections when an object is destroyed. Explicit cleanup is therefore not required. However, it may be desired since there’s no guarantee about when the garbage collector will get around to calling your destructor. This means it’s usually a good idea to provide an explicit way to clean up the resources.
It’d be easy to add a function with a suggestive name like Dispose()
or Destroy()
like we do in AS3 and manually call it when we want to clean up. In fact, the System.IDisposable
interface is there to standardize just that:
class File : IDisposable { public File(string path) { // ... open the file } public void Dispose() { // ... close the file } ~File() { // ... close the file } }
If you adhere to the standard IDisposable
interface by implementing it, you get access to a nifty language feature: using
blocks. These blocks allow you to declare an IDisposable
object scoped to only the block and have its Dispose()
automatically called at the end of the block. Here’s how it works:
using (File someFile = new File("/path/to/some/file")) { // ... use the file } // ... the File's Dispose() is implicitly called here
In AS3, neither of these language features exists. You must explicitly declare a Dispose()
-style function and remember to call it when you’re done:
var someFile:File = new File("/path/to/some/file"); // ... use the file someFile.Dispose();
The only saving grace is that destructors/finalizers and using
/IDisposable
are relatively rare as they mostly handle edge cases like file handles and other system resources. Usually, you can simply rely on the garbage collector to do its job after you’ve released all of your references to an object.
That wraps up today’s topic on resource allocation and cleanup. To summarize, here’s a side-by-side of today’s topics in C# and AS3:
//////// // C# // //////// // Initialize a class or struct instance Point p = new Point(); // Initialize a 1D array int[] many = new int[200]; // Initialize a 1D array of specific values int[] specific = {1, 2, 3}; // Initialize a 2D array int[,] many2D = new int[2,3]; // Initialize a 2D array of specific values int[,] specific = {{1,2,3}, {4,5,6}}; class File : IDisposable { // Explicitly release resources public void Dispose() { } // Implicitly release resources ~File() { } } // Auto-release resources at end of block using (File someFile = new File("/path/to/some/file")) { }
///////// // AS3 // ///////// // Initialize a class or struct instance var p:Point = new Point(); // Initialize a 1D array var many:Vector.<int> = new Vector.<int>(200); // Initialize a 1D array of specific values var specific:Vector.<int> = new <int>[1, 2, 3]; // Initialize a 2D array var many2D:Vector.<int> = new <int>(new Vector.<int>(3), Vector.<int>(3)); // Initialize a 2D array of specific values var specific:Vector.<Vector.<int>> = new <int>[new <int>[1,2,3], new <int>[4,5,6]]; class File { // Explicitly release resources public void dispose() { } // Implicitly release resources // {impossible in AS3} } // Auto-release resources at end of block // {impossible in AS3} // //
We’ll pick up again next week by covering C#’s built-in SQL-style syntax for querying normal objects like arrays. Stay tuned!
Spot a bug? Have a question or suggestion? Post a comment!