System Members and Data 2020
The Environment class allows us to obtain a number of details related to the operating system via various static members.
Here is our new code:
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine("System.Environment Class"); Console.WriteLine(); ShowEnvDetails(); Console.ReadLine(); return -1; } static void ShowEnvDetails() { foreach (string drv in Environment.GetLogicalDrives()) Console.WriteLine("Drive: {0}", drv); Console.WriteLine("OS: {0}", Environment.OSVersion); Console.WriteLine("# of processors: {0}", Environment.ProcessorCount); Console.WriteLine(".NET Version: {0}", Environment.Version); } } }
The output is:
System.Environment Class Drive: C:\ Drive: D:\ Drive: E:\ Drive: F:\ Drive: Y:\ Drive: Z:\ OS: Microsoft Windows NT 6.0.6001 Service Pack 1 # of processors: 2 .NET Version: 4.0.30319.1
C# defines an intrinsic set of data types, which are used to represent local variables, member variables, return values, and input parameters. However, unlike other programming languages, these keywords are much more than simple compiler-recognized tokens. Rather, the C# data type keywords are actually shorthand notations for full-blown types in the System namespace. Following table lists each system data type, its range and the corresponding C# keyword.
TABLEC# Shorthand | System Type | Usage | Range |
---|---|---|---|
bool | System.Boolean | boolean | true, false |
byte | System.Byte | 8 bit integer | 0 - 255 |
char | System.Char | 16 bit Unicode character | /u0000 - /uffff |
decimal | System.Decimal | 128 bit decimal | +/-1.0x10-28 to +/-7.9x10+28 precision of 28-29 digits |
double | System.Double | 64 bit floating point | -1.79769313486232e308 to 1.79769313486232e308 |
float | System.Single | 32 bit floating point | +/-1.5x10-45 to +/-3.4x10+38 precision of 7 digits |
int | System.Int32 | 32 bit integer | -2,147,483,648 to 2,147,483,647 |
long | System.Int64 | 64 bit integer | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
sbyte | System.SByte | 8 bit integer | -128 to 127 |
short | System.Int16 | 16 bit integer | -32,768 to 32,767 |
string | System.String | - | immutable, specified length |
object | System.Object | The base class of all types | Can store any type in an object variable |
uint | System.UInt32 | 32 bit unsigned integer | 0 to 4,294,967,295 |
ulong | System.UInt64 | 64 bit unsigned integer | 0 to 18,446,744,073,709,551,615 |
ushort | System.UInt16 | 16 bit unsigned integer | 0 to 65,535 |
Each of the numerical types (short, int, etc.) map to a corresponding structure in the System namespace. The structures are value types allocated on the stack. On the other hand, string, and object are reference types, meaning the variable is allocated on the managed heap. Value types can be allocated into memory very quickly and have a very fixed and predictable lifetime.
Since the C# bool keyword is simply a shorthand notation for the System.Boolean structure, it is possible to allocate any data type using its full name as shown below:
bool b1 = true; System.Boolean b2 = false;
All intrinsic data types support default constructor. This allows us to create a variable using the new keyword, which automatically sets the variable to its default value:
- bool types are set to false.
- Numeric data is set to 0 or 0.0 in the case of floating-point data types.
- char types are set to a single empty character.
- DataTime types are set to 1/1/1001 12:00:00 AM.
- Object references including strings are set to null.
The following is an example:
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Using_new(); Console.ReadLine(); return -1; } static void Using_new() { Console.WriteLine("Using new:"); bool b=new bool(); int i = new int(); double d = new double(); DateTime dt=new DateTime(); Console.WriteLine("{0}, {1}, {2}, {3}", b, i, d, dt); Console.WriteLine(); } } }
With an output:
Using new: False, 0, 0, 1/1/0001 12:00:00 AM
The primitive .NET data types are arranged in a class hierarchy. Types at the top of a class hierarchy provide default behaviors that are granted to the derived types.
Each of these types derives from System.Object, which defines a set of methods such as ToString(), Equals(), GatHashCode(), etc. They are common to all types in the .NET base class libraries.
Many numerical data types derive from System.ValueType. Descendents of ValueType are automatically allocated on the stack and therefore have a very predictable lifetime and are quite efficient. On the other hand, types that do not have System.ValueType in their inheritance hierarchy (such as System.Type, System.String, System.Array, System.Exception, and System.Delegate are not allocated on the stack but on the garbage-collected heap.
The following code is perfectly legal syntax, given that System.Int32 (the C# int) eventually derives from System.Object and therefore can invoke any of its public members as shown below.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Show_Object_Functionality(); Console.ReadLine(); return -1; } static void Show_Object_Functionality() { Console.WriteLine("System Object Functionality:"); Console.WriteLine("2010.GetHashCode() = {0}", 2010.GetHashCode()); Console.WriteLine("2010.Equals(2011) = {0}", 2010.Equals(2011)); Console.WriteLine("2010.ToString() = {0}", 2010.ToString()); Console.WriteLine("2010.GetType() = {0}", 2010.GetType()); Console.WriteLine(); } } }
The output is:
System Object Functionality: 2010.GetHashCode() = 2010 2010.Equals(2011) = False 2010.ToString() = 2010 2010.GetType() = System.Int32
Numerical types of .NET support MaxValue and MinValue properties that provide information regarding the range a given type can store. Here is an example showing additional members.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Show_Data_Functionality(); Console.ReadLine(); return -1; } static void Show_Data_Functionality() { Console.WriteLine("Data Type Functionality:"); Console.WriteLine("Max of int: {0}", int.MaxValue); Console.WriteLine("Min of int: {0}", int.MinValue); Console.WriteLine("Max of double: {0}", double.MaxValue); Console.WriteLine("Min of double: {0}", double.MinValue); Console.WriteLine("double.Epsilon: {0}", double.Epsilon); Console.WriteLine("double.PositiveInfinity: {0}", double.PositiveInfinity); Console.WriteLine("double.NegativeInfinity: {0}", double.NegativeInfinity); Console.WriteLine("bool.FalseString: {0}", bool.FalseString); Console.WriteLine("bool.TrueString: {0}", bool.TrueString); Console.WriteLine(); } } }
The output is:
Data Type Functionality: Max of int: 2147483647 Min of int: -2147483648 Max of double: 1.79769313486232E+308 Min of double: -1.79769313486232E+308 double.Epsilon: 4.94065645841247E-324 double.PositiveInfinity: Infinity double.NegativeInfinity: -Infinity bool.FalseString: False bool.TrueString: True
C#'s string and char keywords are simple shorthand notations for System.String and System.char both of which are Unicode. Here is an example code:
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Show_Data_Functionality(); Console.ReadLine(); return -1; } static void Show_Data_Functionality() { Console.WriteLine("Character and String Type Functionality:"); char myChar = 'a'; Console.WriteLine("char.IsDigit('a'): {0}", char.IsDigit(myChar)); Console.WriteLine("char.IsLetter('a'): {0}", char.IsDigit('a')); Console.WriteLine("char.IsWhiteSpace('Hello World!', 5): {0}", char.IsWhiteSpace("Hello World!", 5)); Console.WriteLine("char.IsWhiteSpace('Hello World!', 7): {0}", char.IsWhiteSpace("Hello World!", 7)); Console.WriteLine("char.IsPunctuation('!'): {0}", char.IsPunctuation('!')); Console.WriteLine(); bool b = bool.Parse("True"); Console.WriteLine("Value of b: {0}", b); double d = double.Parse("3.14"); Console.WriteLine("Value of d: {0}", d); int i = int.Parse("2010"); Console.WriteLine("Value of i: {0}", i); char c = Char.Parse("w"); Console.WriteLine("Value of c: {0}", c); Console.WriteLine(); } } }
Output of the code:
Character and String Type Functionality: char.IsDigit('a'): False char.IsLetter('a'): False char.IsWhiteSpace('Hello World!', 5): True char.IsWhiteSpace('Hello World!', 7): False char.IsPunctuation('!'): True Value of b: True Value of d: 3.14 Value of i: 2010 Value of c: w
When we prefix a string literal with the @ symbol, we have created a verbatim string. Using verbatim strings, we disable the processing of a literal's escape characters and print out a string as is.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Show_Verbatim_Functionality(); Console.ReadLine(); return -1; } static void Show_Verbatim_Functionality() { Console.WriteLine("Verbatim Functionality:"); Console.WriteLine(@"C:\MyDrectory\myCSharp.cs"); Console.WriteLine( @"This is multiline strings string 1 string 2"); Console.WriteLine(@"John McAfe said in1988, ""The problem of viruses is temporary and will be solved in two years"" "); } } }
Output is:
Verbatim Functionality: C:\MyDrectory\myCSharp.cs This is multiline strings string 1 string 2 John McAfe said in1988, "The problem of viruses is temporary and will be solved in two years"
Once we assign a string object with its initial value, the character data cannot be changed. Sting type defines a number of methods that appear to modify the character data in one way or the other such as uppercasing, lowercasing, and so on. But, if we look more closely at what is happening behind the scenes, we'll notice the methods of the string type are actually returning us a brand-new string object in a modified format.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Show_Strings_are_Immutable(); Console.ReadLine(); return -1; } static void Show_Strings_are_Immutable() { string s1 = "Once you're over the hill you begin to pick up speed."; Console.WriteLine("s1 = {0}", s1); string uString = s1.ToUpper(); Console.WriteLine("uString = {0}", uString); Console.WriteLine("s1 = {0}", s1); } } }
With an output:
s1 = Once you're over the hill you begin to pick up speed. uString = ONCE YOU'RE OVER THE HILL YOU BEGIN TO PICK UP SPEED. s1 = Once you're over the hill you begin to pick up speed.
The original string object, s1 is not uppercased when calling ToUpper(). Rather we are returned a copy of the string in a modified format.
The same law of immutability holds true when we use the C# assignment operator. To demonstrate, comment out any existing code of the previous example, and add the following code:
static void Show_Strings_are_Immutable() { string s1 = "Once you're over the hill you begin to pick up speed."; s1 = "New string value"; }
Load the assembly into ildasm.exe after the compile.
Then, double-click Show_Strings_are_Immutable() method, we get CIL code as shown in the picture below.
Although we have yet to examine the low-level details of the Common Intermediate Language (CIL), note that the Show_Strings_are_Immutable() method makes numerous calls to the ldstr (load string) opcode. The ldstr opcode of CIL loads a new string object on the managed heap. The previous string object that contained the value Once you're over the hill you begin to pick up speed. will eventually be garbage collected.
The string type can be inefficient and result in bloated code if misused, especially performing string concatenation. If we need to represent basic character data such as first or last name, the string data type is the perfect choice.
But if we are building an application that makes heavy use of texture data such as a word processing program, it would be a bad idea to represent the word processing data using string types. Because we will most certainly end up making unnecessary copies of string data.
StringBuilder defines methods that allow us to replace or format segments. To use this type, we need to import the proper namespace:
using System.Text;
When we call members of StringBuilder, we are directly modifying the object's internal character data instead of obtaining a copy of the data in a modified format. When we create an instance of the StringBuilder, we can supply the object's initial startup values via one of many constructors. Let's look at the code which shows the usage of StringBuilder.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { static int Main(string[] args) { Console.WriteLine(); Show_StringBuilder_Functionality(); Console.ReadLine(); return -1; } static void Show_StringBuilder_Functionality() { StringBuilder sb = new StringBuilder("Simplicity, "); sb.AppendLine("carried to the extreme, "); sb.AppendLine("becomes elegance!"); sb.Append("\n"); sb.AppendLine("Jon Franklin"); Console.WriteLine(sb.ToString()); sb.Replace("!", "."); Console.WriteLine(sb.ToString()); Console.WriteLine("sb has {0} chars.", sb.Length); Console.WriteLine(); } } }
Output is:
Simplicity, carried to the extreme, becomes elegance! Jon Franklin Simplicity, carried to the extreme, becomes elegance. Jon Franklin sb has 72 chars.
As we see, we are appending to the internal buffer, and are able to replace (or remove) characters. By default, a StringBuilder is only able to hold a string of 16 characters or less. But this initial value can be changed via an additional constructor argument.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization