Basic .NET types
First things first: C# and VB.NET are strongly typed languages. That means the compiler
must be absolutely certain of the type of an object at all times and types used
in assignment to a variable or as parameters in a function must be of the same type
or of a type inheriting from the expected type. If that is not the case then the
compiler will throw an error.
There are two categories of type values: Reference types (classes) and Value types
(structs). Both classes and structs can have methods and properties assigned to
them so there doesn't need to be all that much difference between them if you don't
want there to be. It's very important to know what you're working which you're working
with at any one time though as it makes the difference between assignment to a variable
causing a copy of the value to be assigned (as happens with Value types) and a copy
of a reference to the object being assigned (as happens with a reference type)
Value Types
Value types are exactly what they sound like - types that store a specific value.
Value types include data types you may well know more commonly as int, float, decimal,
char etc, however those keywords actually refer to more technical structure names
in the .NET framework.
Currently a full list of built-in value types exists
here, but that link may be broken by the time you read this as Microsoft
have an irritating habit of moving everything in MSDN around on a regular basis.
The table below should give a brief list of the most common examples of value types.
|
Keyword
|
.NET Struct Name
|
Description
|
|
bool
|
System.Boolean
|
true or false
|
|
byte
|
System.Byte
|
8 bit unsigned integer
|
|
char
|
System.Char
|
16 bit Unicode character
|
|
short
|
Int16
|
16 bit signed integer
|
|
int
|
Int32
|
32 bit signed integer
|
|
long
|
Int64
|
64 bit signed integer
|
|
float
|
System.Single
|
32 bit floating-point number
|
|
double
|
System.Double
|
64 bit floating-point number
|
|
decimal
|
System.Decimal
|
A decimal number
|
|
DateTime
|
System.DateTime
|
Date and time of day
|
|
TimeSpan
|
System.TimeSpan
|
Difference between two datetimes
|
Along with those types, there are various signed, unsigned and shortened variants
that would be better off retrieved from MSDN than here. Also, when it comes to dealing
with databases later on try not to forget that there are different data types for
SQL values (e.g. System.Data.SqlTypes.SqlInt32), though these are most likely to
be wrapped for you if you do things the ".net" way.
Whenever you work with a value type you gain access to all the methods associated
with that datatype structure (again, these are best discovered using the MSDN .NET
Framework library - just find the documentation for the type you're after, scroll
down to the bottom of the page and click on the "Members" link to find a list of
the methods supported by that data type). A good example of this is the DateTime
data type which has a number of functions to make life easier for you, such as the
"AddHours", "AddMinutes", "AddDays" etc. functions, which save all kinds of mathmatical
grief you'd have had if the DateTime wasn't wrapped properly. As an aside, just
remember that if you're doing maths with DateTime objects you'll probably get a
TimeSpan back, not a DateTime.
You can also define your own value types by declaring structs. Structs are very
similar to classes and are declared in a very similar fashion, even down to the
inclusion of a constructor. You can even change a class into a struct by simply
changing the keyword "class" to the keyword "struct". In theory you should use structs
anywhere that a class logically represents a single value, will be less than 16
bytes, will not be changed after creation and will not be cast to a reference type.
Another value type you should be aware of is Enumerations. These are immensely handy
for making code much more readable and limiting functions down to a pre-defined
set of choices. An enumeration is quite simply a list of fixed values. In theory
you can specify the underlying data type for an enumerations values but unless there
is a compelling reason to do this you should avoid it as it is bad coding practice
(if something needs to be assigned a specific value then it is unlikely that you
want to use an enumeration and you should instead be using a class filled with constants
of the appropriate type as that will better capture your requirements and promote
better code maintainability).
You can declare an enumeration using the following syntax in C#: enum MyValues {
Value1, Value2, Value3 }. If you absolutely must declare a type for the enumeration
then use enum MyValues : int { Value1... .
If you use enumerations often enough you'll eventually want to loop through the
list of values and do something with them. Use the "System.Enum" class to gain programmatic
access to the members of an enumeration (e.g Enum.GetValues(typeof(MyValues));).
Similarly you can use the Enum class to turn a value into a variable with the same
type as the Enumeration using "Enum.Parse(typeof(MyValues), "Value1");".
Half way between the Reference and Value types
Here's where it gets a bit tricky: strings. Strings are technically reference types
but the .net runtime treats them as value types. This is important since you don't
pass around references to strings, you pass around string values but a string can
be set to null. Other than that, it doesn't make much difference but it's definitely
something important you should know. Part of the reason it's important is the performance
hit it causes - it makes creation of strings and string operations more expensive.
You should use a StringBuilder if you're building up a string gradually or as part
of a loop (though be sensible - you only need it if you're doing more than say 5
string concatenations).
There's another "half-way house" exception here and that's the introduction of nullable
value types which came in as part of .NET 2. This doesn't break any fundamental
rules, it just wraps a value type in a class that can take a null value (for those
that are interested, this is implemented using Generics by the Nullable<T>
type). This is highly useful functionality however and is pretty easy to use right
from the outset. If you want to declare a value type as nullable you can simply
declare the type with a question mark after the type keyword (e.g int? x = null)
in C#, otherwise you should declare the type as a Nullable generic in the normal
way. The only problem this introduces is that you are no longer working with the
basic value type, so if you want to do any operations that operate entirely on that
basic value type you'll have to use the .Value property to get the basic, non-nullable
type (obviously you'll need to check .HasValue first to make sure it's not null);
Other than that though this should wrap the value type nicely for you and just add
the ability to assign and check for null.
Reference Types
Reference types tend to be classes. As I mentioned earlier, assigning values to
a variable that is a reference type causes that variable to store a pointer to that
value and does not copy that value, hence any change you make to an object referenced
from multiple places will be reflected in all the places that object is referenced
from.
Reference types are generally created through use of a constructor on the class
itself though sometimes you may have to invoke a function from a factory class in
order to generate an instance of a specific type (remember: if a class is static
you won't be able to create an instance of it).
I'm afraid describing the syntax of a class definition and what a constructor is
sits outside the remit of this guide.
Arrays
Arrays are declared in C# using square brackets after a type declation (e.g int[]
x;). To assign an initial set of values to an array simply declare a comma seperated
list in curly braces (e.g int[] x = { 5, 6, 2 }). Arrays, like other .NET basic
types, have an intrinsic set of functions and properties associated with them that
are always available (these are the members of System.Array) including the Count
property which will give you the length of an array and the Sort method, which sorts
the array.
Exceptions
I'll cover error handling more later on, but it's worth noting that exceptions are
also handled as reference types in the .NET framework and can be passed around and
created like any other class.
Type Types
This is getting ahead of myself a bit, but sometimes you need to pass a type as
a parameter to a function. If you happen to need to do this early in your life as
a .net developer it's worth knowing that the compiler is actually looking for a
class that represents the type, not just the keyword for that type. The syntax you
need in order to handle types as programmatic objects is "typeof(typeKeyword)" (e.g.
typeof(int) or typeof(string)). This will come in handy if you are specifying your
own datatable structures.
Converting between Types
In general you can convert from one numeric type to another numeric type without
worrying about it if you're converting to a type with higher precision (e.g. int
to double), however you can't implicitly make the conversion the other way round.
There are a few options available to you if you want to convert between types but
the method chosen will depend on the situation you're in. It may be that, for whatever
reason, you've got an object stored as one type but you already know that it is
an instance of a more specific type in which case all you have to do is cast the
object to the type that you want it to be. In C# all you have to do to cast an object
is put the type in brackets in front of the object name (e.g. object x = new DateTime(2008,
5, 1); DateTime adate = (DateTime)x;). This was much handier before generics came
along but is still very useful to know.
If you need to take the value of one type and convert it to a different type that
isn't simply a more accurate specification of that type then you need to convert
the data. If that's the case then you can use System.Convert or you can call .ToString
on the object and then call the .Parse or .TryParse functions of the type you're
trying to convert to or you can use the cast operator.
Using the System.Convert class gives you a whole raft of overloaded functions that
take a mutlitude of different types and pass out a value of the type you want to
convert to. For example, if I had a char and I wanted to convert it to a byte I
could call System.Convert.ToByte(myChar). It's fairly self explanatory, just type
System.Convert. into Visual Studio and the autocomplete functionality should show
you everything you need to know.
The form of conversion I find myself using most often is the TryParse method since
generally I need to convert string values to other types. There are
two functions on most numeric types that do basically the same thing; .Parse takes
a string and converts it to the appropriate type and returns the value but it will
throw an exception if it fails. .TryParse returns true of false depending on its
success and returns the value of the conversion using an out parameter (e.g. string
x = "5"; int y; bool convertSucceded = int.TryParse(x, out y); ). Since throwing
exceptions is a performance hit you should use TryParse unless you are absolutely
certain you won't encounter failed conversions.