Miscellaneous BCL Support

Miscellaneous BCL Support

The BCL offers a number of utility and support classes to make common programming tasks simpler and to interact with system services. This section will walk through each of these types.


When converting numbers into strings using the ToString method, a default formatting pattern is used. Each of the numeric types also provides an overload of ToString that takes a string format argument, enabling you to supply a formatting specifier or formatting pattern to control the appearance of the resulting string. The numeric types use the System.Globalization.NumberFormatInfo class to implement formatting functionality. DateTimes use the DateTimeFormatInfo type. Both are culture-aware and will attempt to use the appropriate characters based on the current locale. We will only cover the basic functionality without regard for internationalization in this chapter; for more details on internationalization in general, please refer to Chapter 8. All examples assume a us-EN culture and default culture-specific behavior.

This table shows the formatting specifiers that NumberFormatInfo supports:






Formats a string using the culture-aware currency attributes, such as symbols (money, separator), and significant number of digits.



Formats an integral as a decimal using culture information for negative and precision information.



Uses scientific/engineering formatting, for FigureE+10.


Fixed Point

Formats in fixed-point format, ensuring that at least one digit appears to the left of the decimal point.



Default formatting that uses either fixed-point or scientific based on the storage capabilities of the target instance.



Generic number formatting that uses culture information for decimal and group separators.



Scales a number by 100 and formats it as a percentage.


Round Trip

Formats a number such that parsing the resulting string won't result in any loss of precision.



Formats a number as its base-16 equivalent.

This code snippet illustrates these prebuilt formatting specifiers:

Console.WriteLine("C: {0}", 39.22M.ToString("C"));
Console.WriteLine("D: {0}", 982L.ToString("D"));
Console.WriteLine("E: {0}", 3399283712.382387D.ToString("E"));
Console.WriteLine("F: {0}", .993F.ToString("F"));

Console.WriteLine("G: {0}", 32.559D.ToString("G"));
Console.WriteLine("N: {0}", 93823713.ToString("N"));
Console.WriteLine("P: {0}", .59837.ToString("P"));
Console.WriteLine("R: {0}", 99.33234D.ToString("R"));
Console.WriteLine("X: {0}", 369329.ToString("X"));

Executing this code prints the following output when using the us-EN culture:

C: $39.22
D: 982
E: 3.399284E+009
F: 0.99
G: 32.559
N: 93,823,713.00
P: 59.84 %
R: 99.33234
X: 5A2B1

The various available formatting APIs, such as String.Format and Console.WriteLine, allow you to supply formatting specifiers inline. We saw this above. Just use {n:X} instead of {n}, where X is one of the above specifiers. For example, Console.WriteLine("{0:X}", 369329) is identical to Console .WriteLine("{0}", 369329.ToString("X")).

You can also generate a custom formatting pattern with sequences of the following formatting characters:




Copies the digit in the given position where this character appears, or the character '0' if there is no such digit.


Copies the digit in the given position where this character appears, or nothing if there is no such digit.


Copies '.', and also indicates that preceding characters apply to the whole part of a number, while the following are for the fractional part.


Copies '%' and results in the number being scaled by 100.

E0, E+0, E-0

Results in formatting the number in scientific engineering notation.


Separates patterns into up to three sections. The first is used for positive, the second for negative, and the last for zero-valued numbers.

'...', "...", others

Literal string between '...' or "..." is copied as is. Any other character not present above is treated as a literal.

Date-formatting patterns are different from the numeric ones examined above. Here is a reference of the components of such patterns:





Hour in 12-hour format (1–12)

10, 3


Hour in 12-hour format with leading zero for single digit hours (01–12)

10, 03


Hour in 24-hour format (0–24)

22, 8


Hour in 24-hour format with leading zero for single digit hours (00–24)

22, 08


Minute of the hour (0–60)

47, 2


Minute of the hour with leading zero for single-digit minutes (00–60)

47, 02


Second of the minute (0–60)

35, 6


Second of the minute with leading zero for single-digit seconds (00–60)

35, 06


The AM/PM value for the given time as a single character (A or P)



The AM/PM value for a given time (AM or PM)



Day of the month (1–31)



Day of the month with leading zero for single-digit days (01–31)



Abbreviated name of day of the week



Full name of day of the week



Month as a number (1–12)

10, 3


Month as a number with leading zero for single digit months (01–12)

10, 03


Abbreviated name of the month



Full name of the month



Year without century (0–99)



Year without century and with leading zero for parts less than 10 (00–99)



Year including century



The period or era (A.D. or B.C.)



The time zone hour UTC offset



The time zone hour UTC offset with leading zero for single-digit offsets



The time zone hour and minute UTC offset


'...', "...", others

Literal characters to copy into the output without translation


And just as with number formatting, there is a set of prebuilt single-character specifiers that you may use. By default, DateTime's ToString method will use the ShortDate + LongTime (i.e., G) pattern. The sample outputs in this table assume a new DateTime(2004, 10, 9, 22, 47, 35, 259) using a local Pacific Standard Time (PST) time zone:











dddd, dd MMMM yyyy

Saturday, October 09, 2004



dddd, dd MMMM yyyy HH:mm tt

Saturday, October 09, 2004 10:47 PM



dddd, dd MMMM yyyy HH:mm:ss tt

Saturday, October 09, 2004 10:47:35 PM



MM/dd/yyyy HH:mm tt

10/9/2004 10:47 PM



MM/dd/yyyy HH:mm:ss tt

10/9/2004 10:47:35 PM




October 09



yyyy'-'MM'-'dd'T' HH':' mm':'ss.fffffffK




ddd, dd MMM yyyy HH':' mm':'ss 'GMT'

Sat, 09 Oct 2004 22:47:35 GMT



yyyy'-'MM'-'dd'T'HH':' mm':'ss




HH:mm tt

10:47 PM



HH:mm:ss tt

10:47:35 PM



yyyy'-'MM'-'dd HH':' mm':'ss'Z'

2004-10-09 22:47:35Z



dddd, dd MMMM yyyy

Sunday, October 10, 2004



yyyy MMMM tt

5:47:35 AM

The following code iterates through all of the available prebuilt patterns and prints them to the console; it also prints the result of applying them to a DateTime instance, both using the single character specifier and also by passing their pattern directly:

DateTime dt1 = new DateTime(2004, 10, 9, 22, 47, 35, 259);
DateTimeFormatInfo di = new DateTimeFormatInfo();
for (char c = 'a'; c <= 'z'; c++)
        foreach (string s in di.GetAllDateTimePatterns(c))

            Console.WriteLine("'{0}': {1} - {2}/{3}", c, s,
                dt1.ToString(c.ToString()), dt1.ToString(s));
        char cUpper = Char.ToUpper(c);
        foreach (string s in di.GetAllDateTimePatterns(cUpper))
            Console.WriteLine("'{0}': {1} - {2}", cUpper, s,
                dt1.ToString(cUpper.ToString()), dt1.ToString(s));
    catch (ArgumentException)
        // Ignore--specifier not found.



All of the primitives have natural mappings to simple string values, that is, via ToString. Thus, it makes sense to do the reverse: parse primitive values from strings. A static Parse method exists found on all of the scalar primitives, i.e., integrals, floating points, Decimal, Char, and Boolean. It takes a string and returns its logical equivalent in the target type. There are a few overloads for this method to deal with globalization and cultures. If the string value cannot be parsed successfully as the target type, a FormatException will instead be thrown:

int i = int.Parse("550");
float f = float.Parse("21.99328");

bool b = bool.Parse("True");

int j = int.Parse("bad format"); // Throws a FormatException...

Having to catch a FormatException any time a parse fails because of a formatting problem is not very convenient. In fact, when processing user input, getting a string in the wrong format is not exceptional at all — it happens all the time. Consider the code pattern required:

string s = /*get some user input*/;
int i;
    i = int.Parse(s);
    // Use 'i'...
catch (ArgumentException)

    // Handle the error; e.g. tell the user input was invalid.


This is rather tedious to write over and over again, especially when dealing with a lot of user input, for example in a Windows Forms or ASP.NET application. Furthermore, if you're parsing a large number of input strings, any type of failure rate can dramatically impact the performance of your application. This is because throwing an exception is expensive.

To make this common coding idiom more straightforward and efficient, the TryParse pattern was born. It attempts to parse an input string and returns true or false to indicate success or failure. The parsed value is communicated with an output parameter:

string s = /*get some user input*/;
int i;
if (int.TryParse(s, out i))
    // Use 'i'...
    // Handle the error; e.g. tell the user input was invalid.

Dates may be parsed using an exact format, by using the ParseExact method:

DateTime dt1 = DateTime.ParseExact("Oct 10 2004", "MMM DD yyyy",

There is also a TryParseExact method that follows the pattern you saw above.

Primitive Conversion

You can coerce from one primitive data type to another using widening and narrowing casts. The former happens when you are assigning from a type that has a smaller storage capacity than the target type, and it happens automatically during assignment. You don't run the risk of losing precision in this case (casting from a signed data type to unsigned excluded). Narrowing, on the other hand, occurs when you attempt to coerce a type that has a greater storage capacity to one that is lesser, for example from a long to an int. In this case, you lose precision and can lose information. Most languages require that you explicitly cast to perform these operations.

The Convert class also offers a huge number of static methods to perform such conversions for you. This alleviates the problem of having to work with language-specific conversion mechanisms. The class has a large number of ToXxx(...) methods, where Xxx is the type to which you wish to convert. Each has a number of overrides that take legal data types from which to convert. There is also a set of ChangeType methods, which can be used for general-purpose conversions. For example, uint Convert.ToUInt32(int value) takes in an int value and returns its value as a uint. It detects lossy conversions (e.g., attempting to convert a negative int) and responds by throwing an exception.

A standard interface IConvertible also exists that, when implemented on a type, exposes a way to convert from the implementing type to any other base primitive type. So for instance, because Int32 implements IConvertible, a call to ToUInt32(null) directly on the Int32 instance would have accomplished the same as the above call to Convert.ToUInt32. Unfortunately, accessing the ToUInt32(null) method requires a cast to IConvertible, meaning a box operation on the target of the method call.

Building Strings

The System.Text.StringBuilder class is a great way to avoid generating tons of little garbage strings as a result of string concatenation. It is useful for large numbers of string concatenations. For simple concatenations of, say, under 10 strings, sticking to ordinary concatenation as described earlier is recommended. This type uses an internal buffer to store and incrementally append text while you are in the process of building a larger string. The buffer automatically grows at an exponential rate; that is, if you try to append a string that would exceed the storage currently available, StringBuilder will automatically double its buffer length.

The fact that StringBuilder is mutable means that you can perform operations on a StringBuilder instance that actually change its state rather than requiring a new instance to be allocated upon each operation. For performance reasons this is often a good thing; especially with large buffers of characters, it'd be a shame if you had to generate an entirely new instance of a StringBuilder each time you appended to or modified its contents. StringBuilder is a great type to use for internal implementation string concatenation, but you will often want to convert the results to a String before sharing within the rest of your program.

Constructing a new StringBuilder using its default no-argument constructor generates a builder with capacity for only 16 characters. This is silly if your intention is to use it for a large number of concatenations (which, as stated above, is its only real use). Thus, you'll likely want to pass an int to the overload, which takes a capacity argument to set the initial buffer capacity to a larger quantity. There are overloads that accept a string as input too; these store the string as the beginning of the buffer's state and set the capacity to the next power of two greater than the length of the string (e.g., a string of length 18 will use 32).

Once you've constructed a new builder, you can append characters to it via the Append method. Similarly, the AppendFormat method uses printf-style formatting arguments (like String.Format). The Insert, Replace, and Remove methods also enable you to modify the existing state of the builder's internal buffer. After you've constructed a builder, you may extract its buffer contents as a string using the ToString method. Overloads are offered that permit you to extract a substring of its contents using index and length arguments.

Garbage Collection

The System.GC class offers a set of static methods, using which you can interact with the CLR's GC. Some rather esoteric operations can be found in this class, but there are also quite a few useful ones available, too. Rarely should you ship production code that interacts with the GC directly, although there are some (limited) times when the ability to do so is crucial. Also note that any data retrieved by any of these operations should be considered statistical and not interpreted as being precise. The GC is constantly at work, and it is common that the data is out of date even before you've obtained it. For a detailed discussion of the CLR's GC, please refer to Chapter 3.

The method long GetTotalMemory(bool forceFullCollection) returns the approximate number of bytes currently allocated to the managed heap. This does not include things such as unboxed value types (which live on the stack) or unmanaged resources that don't make use of the managed heap. The bool parameter forceFullCollection enables you to force the GC to perform a collection, after which the statistics are gathered. If true, the operation actually invokes the Collect method and then returns the value.

Both int GetGeneration(object obj) and int GetGeneration(WeakReference wo) will retrieve the current generation of the argument. The int CollectionCount(int generation) method simply returns the number of times the GC has performed a collection on the specified generation. The operation void WaitForPendingFinalizers will block current execution and ask the GC to execute the entire queue of finalizers. Generations and finalization were explained in Chapter 3.

Performing Collections

With a simple flick of the switch — a call to Collect that is — you can force the GC to do a full collection. By full, this means that every generation will be examined for garbage. If you wish to limit this to a maximum generation, GC.Collect(int generation) will do just that. It only collects unreachable objects from generation 0 up to the specified generation.

This method must be used with caution. The GC uses complex heuristics to monitor memory usage and collect memory intelligently. It is very rare that you need to interact directly with the GC for program correctness. If you do, you're probably trying to compensate for a dire problem in your application. Try to find the root cause of that instead of tweaking the GC.


The void SuppressFinalize(object obj) operation tells the GC to remove obj from the finalizable object queue. This is often used in conjunction with the IDisposable pattern — as discussed earlier in this chapter — to ensure that resources aren't closed up twice. Refer back to coverage there for sample code that uses this method. Should you change your mind after calling this method, you can ask the GC to requeue the object in the finalizable object list with a call to void ReRegisterForFinalize(object obj).

You can also cause the object being finalized to become reachable again by setting a reachable reference to it from within its finalizer, that is, setting some other static member to this. This is called resurrection and is seldom a good practice. It's often used either accidentally, or intentionally to implement some form of object pooling policy. Sophisticated programs and libraries can use it to amortize the cost of creating and destroying objects over the life of an application's execution. But for most applications, it can cause more trouble than is worth exploring.

Another method, void KeepAlive(object obj), enables you extend the reachability of an object. If you call it on an object, it is ineligible for collection from the start of the method leading up to the call to KeepAlive. For objects whose only reference is on the active stack frame, the JIT will report liveness of variables as accurately as it can. Thus, if you pass a reference to an object to unmanaged code (where the GC is unaware of its use) and then don't use that object again in the method, the GC might elect to collect it. This might occur concurrently while unmanaged code is manipulating the object, leading to a crash at best and corruption at worst. Chapter 11 discusses unmanaged interoperability further.

Memory Pressure

The methods void AddMemoryPressure(long) and void RemoveMemoryPressure(long) exist for unmanaged interoperability. Specifically, memory pressure enables you to tell the GC that your object is more expensive than it appears (i.e., its managed heap size). Often a managed object holds references to unmanaged resources (e.g., HANDLEs, void*s) until its Dispose or Finalize method has been run. But the "size" of these unmanaged resources to the GC is simply the size of an IntPtr; if the IntPtr refers to 100MB of memory, for example, this can lead to inaccurate collection heuristics. Such an object might look relatively cheap to have hanging around.

Adding a proportionate amount of memory pressure (and removing it during Dispose and/or finalization) helps the GC to know when it should be reclaiming these things.

class UnmanagedWrapper : IDisposable
    private IntPtr handle;
    private long size;

    public UnmanagedWrapper()
        size = 100*1024*1024;
        handle = AllocateUnmanagedMB(size);


    public void Dispose()

    protected void Dispose(bool disposing)
        FreeUnmanagedMB(handle, size);

You must take care to ensure that your adds and removes are balanced; otherwise, over time the GC will accumulate a greater remaining offset. This can dramatically decrease the effectiveness of collection, leading to instability.

Weak References

Weak references hold references to objects, while still allowing the GC to claim such objects when it performs a collection (if no other strong reference refers to them). Imagine some case when you are holding a large cache of objects in memory using custom data structures and normal references. A properly tuned cache would significantly reduce the average cost of retrieving an object from expensive backing stores multiple times, for example, a database or disk. Unfortunately, if the GC needs to perform a collection because the system is running low on memory, none of these objects will be collected. This requires some complex cache management code.

One possible solution is to use weak references for all references in your cache. This doesn't actually report a strong reference to the instance to the GC, so the objects pointed at can be reclaimed if all other strong references are dropped. Admittedly, this isn't always the semantics you desire. A single GC can wipe out your entire cache. So in the end, you might need to write complex cache management code. A good compromise would be to periodically mark objects in the cache that have been alive for a while, resetting the timer each time a cache hit for an object occurs.

The WeakReference class constructor takes a single parameter: the referent object. This stores the object reference, then accessible using the Target property. WeakReference also has an IsAlive property, a bool that indicates whether the object pointed at is still alive (true) or whether it has been garbage collected (false). You should check this property before accessing any object but must ensure that you eliminate a possible race condition between the check and object extraction. This coding pattern works best:

WeakReference wr = new WeakReference(target);
object o = wr.Target; // Ensure a strong reference is established...
if (wr.IsAlive) {
    // Process the object...We've eliminated the race (because we captured 'o').

Math APIs

The BCL System.Math class provides basic mathematical. This class is static and provides methods such as absolute value, floor and ceiling, rounding and truncation with precise midpoint semantics (i.e., round away from zero, or to even), trigonometric functions, exponential operations, and min and max, among others. There are quite a few methods on this class, most of which are self-explanatory:

public static class Math
    // Methods
    public static decimal Abs(decimal value);
    public static extern double Abs(double value);
    public static short Abs(short value);
    public static int Abs(int value);
    public static long Abs(long value);
    public static sbyte Abs(sbyte value);
    public static extern float Abs(float value);
    public static extern double Acos(double d);
    public static extern double Asin(double d);
    public static extern double Atan(double d);
    public static extern double Atan2(double y, double x);
    public static long BigMul(int a, int b);
    public static decimal Ceiling(decimal d);
    public static extern double Ceiling(double a);
    public static extern double Cos(double d);
    public static extern double Cosh(double value);
    public static int DivRem(int a, int b, out int result);
    public static long DivRem(long a, long b, out long result);
    public static extern double Exp(double d);
    public static decimal Floor(decimal d);
    public static extern double Floor(double d);
    public static double IEEERemainder(double x, double y);
    public static extern double Log(double d);
    public static double Log(double a, double newBase);
    public static extern double Log10(double d);
    public static byte Max(byte val1, byte val2);
    public static decimal Max(decimal val1, decimal val2);
    public static double Max(double val1, double val2);
    public static short Max(short val1, short val2);

    public static int Max(int val1, int val2);
    public static long Max(long val1, long val2);
    public static sbyte Max(sbyte val1, sbyte val2);
    public static float Max(float val1, float val2);
    public static ushort Max(ushort val1, ushort val2);
    public static uint Max(uint val1, uint val2);
    public static ulong Max(ulong val1, ulong val2);
    public static byte Min(byte val1, byte val2);
    public static decimal Min(decimal val1, decimal val2);
    public static double Min(double val1, double val2);
    public static short Min(short val1, short val2);
    public static int Min(int val1, int val2);
    public static long Min(long val1, long val2);
    public static sbyte Min(sbyte val1, sbyte val2);
    public static float Min(float val1, float val2);
    public static ushort Min(ushort val1, ushort val2);
    public static uint Min(uint val1, uint val2);
    public static ulong Min(ulong val1, ulong val2);
    public static extern double Pow(double x, double y);
    public static decimal Round(decimal d);
    public static extern double Round(double a);
    public static decimal Round(decimal d, int decimals);
    public static decimal Round(decimal d, MidpointRounding mode);
    public static double Round(double value, int digits);
    public static double Round(double value, MidpointRounding mode);
    public static decimal Round(decimal d, int decimals,
        MidpointRounding mode);
    public static double Round(double value, int digits,
        MidpointRounding mode);
    public static int Sign(decimal value);
    public static int Sign(double value);
    public static int Sign(short value);
    public static int Sign(int value);
    public static int Sign(long value);
    public static int Sign(sbyte value);
    public static int Sign(float value);
    public static extern double Sin(double a);
    public static extern double Sinh(double value);
    public static extern double Sqrt(double d);
    public static extern double Tan(double a);
    public static extern double Tanh(double value);
    public static decimal Truncate(decimal d);
    public static double Truncate(double d);

    // Fields
    public const double E = 2.7182818284590451;
    public const double PI = 3.1415926535897931;

We won't go into great detail here other than to provide a brief code sample:

Console.WriteLine("Abs({0}) = {1}", -55, Math.Abs(-55));
Console.WriteLine("Ceiling({0}) = {1}", 55.3, Math.Ceiling(55.3));
Console.WriteLine("Pow({0},{1}) = {2}", 10.5, 3, Math.Pow(10.5, 3));

Console.WriteLine("Round({0},{1}) = {2}",
    10.55358, 2, Math.Round(10.55358, 2));
Console.WriteLine("Sin({0}) = {1}", 323.333, Math.Sin(323.333));
Console.WriteLine("Cos({0}) = {1}", 323.333, Math.Cos(323.333));
Console.WriteLine("Tan({0}) = {1}", 323.333, Math.Tan(323.333));

The output that this code produces is:

Abs(-55) = 55
Ceiling(55.3) = 56
Pow(10.5,3) = 1157.625
Round(10.55358,2) = 10.55
Sin(323.333) = 0.248414709883854
Cos(323.333) = -0.968653772982546
Tan(323.333) = -0.256453561440193

Random Number Generation

The System.Random class provides a way to generate a sequence of pseudo-random numbers. For most scenarios, pseudo-random is sufficient. We will examine shortly how to generate cryptographically sound random numbers. The Random class uses a seed to kick off its number generation. When creating an instance, you can choose to supply your own seed using the Random(int seed) constructor, but in most cases the default time-based seed that the no-argument constructor provides is sufficient.

Once you have an instance of Random, you can retrieve a random number from it. The simplest is the int Next() method, which returns an integer bounded by the storage capacity of int. Alternatively, you can limit the range using the int Next(int maxValue) overload. The result of a call to this will always be greater than or equal to 0 and less than maxValue. You can similarly specify a range of both minimum and maximum values using the int Next(int minValue, int maxValue) overload. NextDouble will return a double precision number greater than or equal to 0.0 and less than 1.0. Lastly, the void NextBytes(byte[] buffer) method takes a byte[] and fills it up with pseudorandomly generated bytes.

Psuedo- and Cryptographically Sound Randomness

As mentioned before, Random creates pseudo-random numbers. These are not truly random because the class uses a deterministic algorithm to produce them. In other words, the algorithm goes through the same step-by-step instructions to generate the next number in the sequence. It will always perform fixed transitions from one specific number to the next, based on the intrinsic properties of Random's algorithm. For example, it just so happens that whenever you see the number 553, you always know that the next one will be 1347421470. How do we know this? Try it out!

Random r = new Random(553);

No matter how many times you run this code, the same boring number 1347421470 will be printed out. (Taking a dependency on this would be very bad. The Framework developers might legitimately decide to make a tweak — or perform a bug fix — to Random's algorithm that would change this fact.) To further illustrate this point, consider what happens if we simply instantiate the Random object with a constant seed in the sample from the previous section. The entire sequence is predictable:

Random r = new Random(99830123);
Console.WriteLine(r.Next(9999, 100750));

No matter how many times you run this code, it will allow us to print out the following numbers: 214873035, 75274, 85587, 0.571986661558965. This just proves why using the default constructor's time-based seed is usually a better idea; you will seldom end up with the same seed number twice in close proximity.


More precisely, if you're creating two Random objects, one right after another, they could actually end up with the same time-based seed. This will only happen if they get constructed within a very small period of time (i.e., the same "tick") but is quite probable since the granularity of a tick is several milliseconds.

A handy safeguard to protect against this is to share an instance of Random across your application with a static shared instance. This, too, however, can be a bit problematic because this class isn't thread-safe. If you decide to use this approach, you should look into protecting against threading problems with the standard locking mechanisms offered in the .NET Framework. Refer to Chapter 10 for more details on how to perform thread-safe sharing of static objects.

If you require a strong cryptographically sound random number generator — when working with cryptography algorithms, for instance — the System.Security.Cryptography.RandomNumberGenerator class implements such an algorithm. This class is abstract but offers a static factory method Create() that returns a new RandomNumberGenerator instance. This class operates very similarly to the Random's GetBytes method: you supply a byte[] to void GetBytes(byte[] data), and it simply populates data with as many random byte numbers as the buffer it can hold. GetNonZeroBytes does the same, except that it guarantees the array will contain no 0s. Note that this type's algorithm is several orders of magnitude slower than Random's; thus, only use it if you're sure you need crypto-quality random numbers.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows