How to Search & Replace Strings with C#

If you’re wondering how to search and replace string with C#, then you’re at the right place. Our aim with this tutorial is to give you all the information that you need on how to do this, but we’ve also tried to make it simple and get to the point immediately so that it’s simple to understand and it cuts through the fluff.

In C#, there’s a really easy method to find an old value in a string and replace it (and all instances of it) with a new value of your choice. This method aptly named the Replace method, is fairly straightforward to understand. The syntax is as follows:

public string Replace(
    string oldValue,
    string newValue
)

The best way to understand how to use a method, though, is to see it in action. So let’s say that we have the following string:

String a = "My favorite foods are ice cream and pizza";

But maybe you actually hate pizza, and want to change the word “pizza” to “burgers.” Here’s how you would go about doing it:

a = a.Replace("pizza", "burgers");
Console.WriteLine(a);

So the output of the code above would be: “My favorite foods are ice cream and burgers” written to the console.

When using this code, it’s important to remember that what you want to be replaced will be replaced in ALL instances. So if you had pizza written twice in the string above, then it would have been replaced twice by the word burgers, like this:

String a = "Ice cream is my favorite food. I don't like pizza as much as I like ice cream, but I like pizza more than I like hot dogs";

a = a.Replace("pizza", "burgers");
Console.WriteLine(a);

The output of the code above would be: “Ice cream is my favorite food. I don’t like burgers as much as I like ice cream, but I like burgers more than I like hot dogs.”

Finally, there is also a way to use the Replace method to replace something multiple times. So if you want to replace pizza with burgers for the string in the example above, but then decide you want to replace burgers with cookies immediately after. The code to execute that effect would look something like this (assume that a in the example below is the same string as a in the previous example):

a = a.Replace("pizza", "burgers").Replace("burgers", "cookies");
Console.WriteLine(a);

Your final output of the code above would be “Ice cream is my favorite food. I don’t like cookies as much as I like ice cream, but I like cookies more than I like pizza.”

(Apologies if this tutorial has made you hungry.)

The search and replace method is one that is fairly easy to use. Play around with it and see what kind of strings you can create (maybe ones that aren’t all about food), and how many times you can change the same string so that by the end it doesn’t resemble the original string at all.

We hope that this tutorial has provided you concise yet detailed information about how to search and replace string with C#. I’m sure that it has helped you to understand some things that you might have seemed somewhat complex to grasp.

We’d love to know what you think of this tutorial. Has it helped you? Has it given you all the information that you were looking for? Let us know what you think or hit us up with any questions that you might still have after reading this tutorial.

C# Classes: Lesson 7 Serves as an Introduction

This lesson introduces you to C# Classes. We have included all the necessary aspects to provide an introduction to the topic. Our objectives with C# class are as follows:

  • Implement Constructors.
  • Know the difference between instance and static members.
  • Understand Destructors.
  • Familiarization with Class Members.

Since the beginning of this tutorial, you have been using classes. By now, you should have a sense of what a class is for and how to specify one. This lesson will build upon what you already know and introduce the various class members.

Classes are declared by using the keyword class, followed by the class name and a set of class members surrounded by curly braces. Every class has a constructor, called automatically at any time an instance of a class is created. The purpose of constructors is to initialize class members when an instance of the class is created. Constructors do not have return values and always have the same name as the class. Listing 7-1 is an example of a class.

Listing 7-1. Example C# Classes: Classes.cs
// Namespace Declaration
using System;

// helper class
class OutputClass 
{
    string myString;

    // Constructor
    public OutputClass(string inputString) 
    {
        myString = inputString;
    }

    // Instance Method
    public void printString() 
    {
        Console.WriteLine("{0}", myString);
    }

    // Destructor
    ~OutputClass() 
    {
        // Some resource cleanup routines
    }
}

// Program start class
class ExampleClass 
{
    // Main begins program execution.
    public static void Main() 
    {
        // Instance of OutputClass
        OutputClass outCl = new OutputClass("This is printed by the output class.");

        // Call Output class' method
        outCl.printString(); 
    }
}
<%--
 

Get Setup Instructions For How to Run this Program

--%>

Listing 7-1 shows two classes. The top class, OutputClass, has a constructor, instance method, and a destructor. It also has a field named myString. Notice how the OutputClass constructor is used to initialize data members of the class. In this case, the OutputClass constructor accepts a string argument, inputString. This string is copied to the class field myString.

Constructors are not mandatory, as indicated by the implementation of ExampleClass. In this case, a default constructor is provided. A default constructor is simply a constructor with no arguments. However, a constructor with no arguments is not always useful. To make default constructors more useful, you can implement them with initializers. Here is an example:

    public OutputClass() : this("Default Constructor String") { }

Imagine this constructor included in class OutputClass from Listing 7-1. An initializer follows this default constructor. The colon, “:”, marks the beginning of the initializer, followed by the this keyword. The this keyword refers to this particular object. It effectively makes a call to the constructor of the same object defined in. After the this keyword is a parameter list with a string.

The action taken by the initializer above is to invoke the OutputClass constructor that takes a string type as an argument. The initializer helps you to ensure your class fields are initialized when a class is instantiated.

The example above illustrates how a class can have multiple constructors. The specific constructor called depends on the number of parameters and the type of each parameter.

Furthermore

In C#, there are two types of class members, instance and static. Instance class members belong to a specific occurrence of a class. Every time you declare an object of a certain class, you create a new instance of that class. The ExampleClass Main() method creates an instance of the OutputClass named outCl.

You can create multiple instances of OutputClass with different names. Each of these instances is separate and stand alone. For example, if you create two OutputClass instances as follows:

    OutputClass oc1 = new OutputClass("OutputClass1");
    OutputClass oc2 = new OutputClass("OutputClass2");

You create two separate instances of OutputClass with separate myString fields and separate printString() methods. On the other hand, if aclass member is static, you can access it simply by using the syntax <classname>.<static class member>. The instance names are oc1 andoc2.

Suppose OutputClass had the following static method:

    public static void staticPrinter() 
    {
        Console.WriteLine("There is only one of me.");
    }

Then you could call that function from Main() like this:

    OutputClass.staticPrinter();

You must call static class members through their class name and not their instance name. That means that you don’t need to instantiate a class to use its static members. There is only ever one copy of a static class member.

Good use of static members is when there is a function performed, and no intermediate state is required, such as math calculations. Matter of fact, the .NET Frameworks Base Class Library includes a Math class that makes extensive use of static members.

Another type of constructor is the static constructor. Use the static constructor to initialize static fields in a class. You declare a static constructor by using the keyword static just in front of the constructor name. A static constructor is called before an instance of a class is created, before a static member is called, and before the static constructor of a derived class (covered in a later chapter). They are called only once.

Furthermore

OutputClass also has a destructor. Destructors look just like constructors, except they have a tilde, “~”, in front of them. They don’t take any parameters and do not return a value. Destructors are places where you could put the code to release any resources your class held during its lifetime. They are normally called when the C# garbage collector decides to clean your object from memory.

Note: You’ve probably noticed the use of the public modifier (an access modifier), meaning that a class member can be accessed from other classes. When used on a class, it means that the class can be accessed by DLLs outside of the Assembly (which is commonly a *.exe or *.dll file). Lesson 19: Encapsulation discusses access modifiers in more depth.

So far, the only class members you’ve seen are Fields, Methods, Constructors, and Destructors. Here is a complete list of the types of members you can have in your classes:

  • Constructors
  • Destructors
  • Fields
  • Methods
  • Properties
  • Indexers
  • Delegates
  • Events
  • Nested Classes

Those items not covered in this lesson will be covered in later lessons for C# Classes.

Final Thoughts About C# Class

In summary, you can declare the instance and static constructors. You know how to initialize class fields. When there is no need to instantiate an object, you can create static class members. You can also declare destructors for cleaning up resources.

A modifier of class is internal by default but it can be public. One should use a class keyword to declare the type class. The identifier must begin with a capitalized letter. A colon precedes the name of the class’ parent. Note that is optional. A colon precedes a comma-separated list of interfaces implemented by the class. Curly braces surround the class body.

I invite you to return for Lesson 8: Class Inheritance.

C# Generics: Introduction to Generic Collections in Lesson 20

In Lesson 02, you learned about arrays and how they allow you to add and retrieve a collection of objects. Arrays are good for many tasks, but C# v2.0 introduced a new feature called generics. Among many benefits, one of the main ones is that generics allow us to create collections that enable us to do more than an array. This lesson on C# Generics will introduce you to generic collections and how to use it. Here are the objectives for this lesson:

  • Understand how generic collections can benefit you.
  • Learn how to create and use a generic List.
  • Write code that implements a generic Dictionary.

C# Generics: What Can Generics Do For Me?

Throughout this tutorial about Generics in C#, you’ve learned about types, whether built-in (int, float, char) or custom (Shape, Customer, Account). In .NET v1.0 there were collections, such as the ArrayList for working with groups of objects.

An ArrayList is much like an array, except it could automatically grow and offer many convenience methods that arrays don’t have. The problem with ArrayList and all the other .NET v1.0 collections is that they operate on type object. Since all objects derive from the object type, you can assign anything to an ArrayList.

Furthermore

The problem with this is that you incur performance overhead converting value type objects to and from the object type and a single ArrayList could accidentally hold different types. That would cause hard to find errors at runtime because you wrote code to work with one type. Generic collections fix these problems.

A generic collection is strongly typed (type safe), meaning that you can only put one type of object into it. This eliminates type mismatches at runtime. Another benefit of type safety is that performance is better with value type objects because they don’t incur the overhead of being converted to and from type object.

With generic collections, you have the best of all worlds because they are strongly typed, like arrays. You also have the additional functionality, like ArrayList and other non-generic collections, without the problems.

Creating Generic List<T> Collections in C#

The pattern for using a List collection in C# is similar to arrays. You declare the List, populate its members, then access the members. Here’s a code example of how to use a List:

    List<int> myInts = new List<int>();

    myInts.Add(1);
    myInts.Add(2);
    myInts.Add(3);

    for (int i = 0; i < myInts.Count; i++)
    {
        Console.WriteLine("MyInts: {0}", myInts[i]);
    }

The first thing you should notice is the generic collection List<int>, referred to as List of int. If you looked in the documentation for this class, you would find it defined as List<T>, where T could be any type. If you wanted the list to work on string orCustomer objects, you could define them as List<string> or List<Customer>. They would hold the only string or Customer objects. In the example above, myInts holds only type int.

Using the Add method, you can add as many int objects to the collection as you want. This is different from arrays, which have a fixed size. The List<T> class has many more methods you can use, such as Contains, Remove and more.

Furthermore

There are two parts of the loop that you need to know about. First, the condition uses the Count property of myInts. This is another difference between collections and arrays in that an array uses a Length property for the same thing. Next, the way to read from a specific position in the List<T> collection, myInts[i], is the exact same syntax you use with arrays.

The next time you use a single-dimension array, consider using a List<T> instead. That said, be sure to let your solution fit the problem and use the best tool for the job. i.e. it’s common to work with byte[] in many places in the .NET Framework.

Working with Dictionary<TKey, TValue> Collections

Another useful generic collection is the Dictionary, which works with key/value pairs. There is a non-generic collection called a Hashtable, which does the same thing, except that it operates on type object. However, you want to avoid the non-generic collections and use their generic counterparts instead. The scenario I’ll use for this example is that you have a list of Customers that you need to work with.

It would be natural to keep track of these Customers via their CustomerID. The Dictionary example will work with instances of the following Customer class:

    public class Customer
    {
        public Customer(int id, string name)
        {
            ID = id;
            Name = name;
        }

        private int m_id;

        public int ID
        {
            get { return m_id; }
            set { m_id = value; }
        }

        private string m_name;

        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }
    }

The Customer class above has a constructor to make it easier to initialize. It also exposes its state via public properties. It isn’t sophisticated at this point. That’s okay because its only purpose is to help you learn how to use a Dictionary collection.  The following example populates a Dictionary collection with Customer objects and then shows you how to extract entries from the Dictionary:

     Dictionary<int, Customer> customers = new Dictionary<int, Customer>();

    Customer cust1 = new Customer(1, "Cust 1");
    Customer cust2 = new Customer(2, "Cust 2");
    Customer cust3 = new Customer(3, "Cust 3");

    customers.Add(cust1.ID, cust1);
    customers.Add(cust2.ID, cust2);
    customers.Add(cust3.ID, cust3);

    foreach (KeyValuePair<int, Customer> custKeyVal in customers)
    {
        Console.WriteLine(
            "Customer ID: {0}, Name: {1}",
            custKeyVal.Key,
            custKeyVal.Value.Name);
    }

The customers variable is declared as a Dictionary<int, Customer>.  Considering that the formal declaration of Dictionary isDictionary<TKey, TValue>, the meaning of customers is that it is a Dictionary where the key is type int and the value is type Customer. Therefore, any time you add an entry to the Dictionary, you must provide the key. That is because it is also the key that you will use to extract a specified Customer from the Dictionary.

I created three Customer objects, giving each an ID and a Name. I’ll use the ID as the key and the entire Customer object as the value. You can see this in the calls to Add, where I added custX.ID as the key (first parameter), as well as adding the custX instance as the value (second parameter).

Extracting information from a Dictionary is a little bit different. Iterating through the customer’s Dictionary with a for each loop, the type returned is KeyValuePair<TKey, TValue>, where TKey is type int, and TValue is type Customer. That is because those are the types that the customer’s Dictionary is defined with.

Furthermore

Since custKeyVal is type KeyValuePair<int, Customer>, it has Key and Value properties for you to read from. In our example,custKeyVal.Key will hold the ID for the Customer instance and custKeyVal.Value will hold the whole Customer instance. The parameters in the Console.WriteLine statement demonstrates this by printing out the ID. That is obtained through the Key property, and the Name, obtained through the Name property of the Customer instance, is returned by the Value property.

The Dictionary type is handy for those situations where you need to keep track of objects via some unique identifier. For your convenience, here’s Listing 20-1, shows how both the List and Dictionary collections work.

Listing 20-1. Introduction to Using Generic Collections with an Example of the List<T> and Dictionary<TKey, TValue> Generic Collections
using System;
using System.Collections.Generic;

public class Customer
{
    public Customer(int id, string name)
    {
        ID = id;
        Name = name;
    }

    private int m_id;

    public int ID
    {
        get { return m_id; }
        set { m_id = value; }
    }

    private string m_name;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<int> myInts = new List<int>();

        myInts.Add(1);
        myInts.Add(2);
        myInts.Add(3);

        for (int i = 0; i < myInts.Count; i++)
        {
            Console.WriteLine("MyInts: {0}", myInts[i]);
        }

        Dictionary<int, Customer> customers = new Dictionary<int, Customer>();

        Customer cust1 = new Customer(1, "Cust 1");
        Customer cust2 = new Customer(2, "Cust 2");
        Customer cust3 = new Customer(3, "Cust 3");

        customers.Add(cust1.ID, cust1);
        customers.Add(cust2.ID, cust2);
        customers.Add(cust3.ID, cust3);

        foreach (KeyValuePair<int, Customer> custKeyVal in customers)
        {
            Console.WriteLine(
                "Customer ID: {0}, Name: {1}",
                custKeyVal.Key,
                custKeyVal.Value.Name);
        }

        Console.ReadKey();
    }
}

Whenever coding with the generic collections, add a using System.Collections.Generic declaration to your file, just as in Listing 20-1.

Final Thoughts

Generic collections give you the best of both worlds with the strong typing of arrays and flexibility of non-generic collections. There are many more generic collections to choose from also, such as Stack, Queue, and SortedDictionary. Look in the System.Collections.Genericnamespace for other C# Generics collections.

The advantages of generics are that they increase the reusability of the code and are safe. The user will get compile-time errors when using a different type of data than specified in the definition. Generics also have a performance advantage due to removing the possibility of boxing and unboxing. Boxing involves converting a type to an object. Unboxing is converting the object to a type. The other definition of unboxing is unwrapping the type from the object container.

I invite you to return for Lesson 21: Anonymous Methods.

C# Struct: Everything You Need to Know in Lesson 12

This lesson will teach you about C# struct. Our objectives are as follows:

  • Understand the Purpose of structs.
  • Implement a struct.
  • Use a struct.

What is a struct?

A struct is a value type. To help understand the struct, it’s helpful to make a comparison with classes, as described in Lesson 7: Introduction to Classes and subsequent chapters. While a struct is a value type, a class is a reference type. Value types hold their value in memory where they are declared, but reference types hold a reference to an object in memory. If you copy a struct, C# creates a new copy of the object and assigns the copy of the object to a separate struct instance.

When you copy a class, C# creates a new copy of the reference to the object and assigns the copy of the reference to the separate class instance. Structs can’t have destructors, but classes can have destructors. Another difference between a struct and class is that a struct can’t have implementation inheritance, but a class can. That’s described in Lesson 8: Class Inheritance. Although a struct can’t have implementation inheritance, it can have interface inheritance, as described in Lesson 13: Interfaces. Lesson 22: Topics on C# Type digs deeper into the differences between value and reference types, which provides code that demonstrates the concepts introduced here.

Furthermore

The .NET Framework includes many types that are structs, including many of the built-in types. For example, a System.Int32 is a C# int, a System.Single is a C# float and a System.Bool is a C# bool. The C# built-in types are aliases for .NET Framework types, giving you language-specific syntax. If you look at the documentation for any of these .NET Framework types, you’ll see them declared as struct types. That means you’ll need to recognize what a struct type is when you see it. The next section helps with that by showing you how to create your own custom struct type.

Creating a Custom struct Type

While the behavior of the class and struct types are very different, their syntax is similar. You declare the type and its members with the primary visual difference being that a struct uses the keyword struct and a class uses the keyword class. The example in Listing 12-1 demonstrates how to define a custom struct. In this case, the struct is a Rectangle with Width and Height properties, similar to what you might use to represent a rectangular shape on a screen.

Listing 12-1. Defining a struct
/// <summary>
/// Custom struct type, representing
    a rectangular shape
/// </summary>
struct Rectangle
{
    /// <summary>
    /// Backing Store for Width
    /// </summary>
    private int m_width;

    /// <summary>
    /// Width of rectangle
    /// </summary>
    public int Width 
    {
        get
        {
            return m_width;
        }
        set
        {
            m_width = value;
        }
    }

    /// <summary>
    /// Backing store for Height
    /// </summary>
    private int m_height;

    /// <summary>
    /// Height of rectangle
    /// </summary>
    public int Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
        }
    }
}

As you can see, the Rectangle struct in Listing 12-1 looks very much like a class with a couple of properties, except that it uses the keyword struct, instead of the keyword class, to declare that Rectangle is a struct.

Using a struct

To use a struct, instantiate the struct and use it just as a class. Listing 12-2 shows how to instantiate the Rectangle struct and access its properties.

Listing 12-2. Using a Struct
using System;

/// <summary>
/// Example of declaring and using
    a struct
/// </summary>
class StructExample
{
    /// <summary>
    /// Entry point: execution starts
        here
    /// </summary>
    static void Main()
    {
        // instantiate a new Rectangle struct
        // where Width is set to 1 and Height
            is set to 3
	Rectangle rect1 = new Rectangle();
        rect1.Width = 1;
        rect1.Height = 3;

        // show the value of Width and Height
            for rect1
        Console.WriteLine("rect1: {0}:{1}", rect1.Width, rect1.Height);

        Console.ReadKey();
    }
}

The code in the Main method of Listing 12-2 instantiates a new Rectangle struct and sets its Height and Width properties. The experience is similar to how a class can be used. Here’s the output:

rect1: 1:3

An alternate way of instantiating a struct and setting its properties is with an object initializer, shown below:

        // you can also use object
    initialization syntax
        Rectangle rect11 = new Rectangle
        {
            Width = 1,
            Height = 3
        };

Notice that the object initializer uses curly braces and sets properties via a comma-separated list of name/value pairs.

Overloading struct Constructors

The two previous examples of instantiating a struct, via constructor only and via object initializer, used the default (parameterless) constructor of the struct. The default constructor is implicitly defined by C#, and you can’t implement the default constructor yourself.  The default constructor initializes all struct fields to default values. i.e. integrals are 0, floating points are 0.0, and booleans are false. If you need custom constructor overloads, you can add new constructors, as long as they have one or more parameters. Listing 12-3 shows customization of the Rectangle struct from Listing 12-1, which includes a constructor overload.

Listing 12-3: Overloading a struct Constructor
/// <summary>
/// Custom struct type, representing
    a rectangular shape
/// </summary>
struct Rectangle
{
    /// <summary>
    /// Backing Store for Width
    /// </summary>
    private int m_width;

    /// <summary>
    /// Width of rectangle
    /// </summary>
    public int Width 
    {
        get
        {
            return m_width;
        }
        set
        {
            m_width = value;
        }
    }

    /// <summary>
    /// Backing store for Height
    /// </summary>
    private int m_height;

    /// <summary>
    /// Height of rectangle
    /// </summary>
    public int Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
        }
    }

 /// <summary> /// Instantiate rectangle struct with
    dimensions /// </summary>
    /// <param name="width">Width
        to make new rectangle</param> 
            /// <param name="height">Height to make new rectangle</param>
    public Rectangle(int width, 
            int height) { m_width = width; m_height = height; }
}

The highlighted portion of code in Listing 12-3 is a constructor overload. Constructors are named the same as their containing struct, which is Rectangle in this case. This Rectangle constructor overload has two parameters, which it assigns to backing stores that are encapsulated by properties for calling code. Listing 12-4 shows an example of how you would use the constructor overload from Listing 12-3 to instantiate a new Rectangle.

Listing 12-4: Instantiating a struct Through a Constructor Overload
using System;

/// <summary>
/// Example of declaring and using
    a struct
/// </summary>
class StructExample
{
    /// <summary>
    /// Entry point: execution starts
        here
    /// </summary>
    static void Main()
    {
        // instantiate a new Rectangle struct
        // where Width is set to 5 and Height
            is set to 7
	Rectangle rect2 = new Rectangle(5, 7);

        // show the value of Width and Height
            for rect2
        Console.WriteLine("rect2: {0}:{1}", rect2.Width, rect2.Height);

        Console.ReadKey();
    }
}

The code in the Main method of Listing 12-4 instantiates a Rectangle struct and displays the values set via the constructor overload. When instantiating rect2, the code passes the values 5 and 7 as arguments. From the constructor in Listing 12-3, you can see that the Width ofrect2 will be set to 5 and the Height of rect2 will be set to 7. Here’s the output from Listing 12-4:

rect2: 5:7

Adding a Method to a struct

All of the examples so far showed how you can add properties and constructors to a struct, but you can also add methods to a struct. Defining a method in a struct is the same as defining a method in a class. Listing 12-5 shows the Rectangle struct with a method named Add.

Listing 12-5: Adding a Method to a struct
/// <summary>
/// Custom struct type, representing
    a rectangular shape
/// </summary>
struct Rectangle
{
    /// <summary>
    /// Backing Store for Width
    /// </summary>
    private int m_width;

    /// <summary>
    /// Width of rectangle
    /// </summary>
    public int Width 
    {
        get
        {
            return m_width;
        }
        set
        {
            m_width = value;
        }
    }

    /// <summary>
    /// Backing store for Height
    /// </summary>
    private int m_height;

    /// <summary>
    /// Height of rectangle
    /// </summary>
    public int Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
        }
    }

    /// <summary>
    /// Instantiate rectangle struct
        with dimensions
    /// </summary>
    /// <param name="width">Width
        to make new rectangle</param>
    /// <param name="height">Height
        to make new rectangle</param>
    public Rectangle(int width, int height)
    {
        m_width = width;
        m_height = height;
    }


    /// <summary> 
    /// Increase the size of this rectangle by the size of the specified rectangle
    /// </summary>
    /// <param name="rect">Rectangle that will be added to this rectangle</param>
    /// <returns>New rectangle created by adding rect to this rectangle</returns>
    public Rectangle Add(Rectangle rect)
    { 
        // create instance of rectangle struct with default constructor
        Rectangle newRect = new Rectangle();

        // add matching axes and assign to new Rectangle struct
        newRect.Width = Width + rect.Width; newRect.Height = Height + rect.Height;

        // return new Rectangle struct
        return newRect; 
    }
}

The highlighted code in Listing 12-5 is a method named Add. It might or might not make sense to add two Rectangle structs together. The example demonstrates how to define a method in a struct. In this case, the Add method will increase the Height and Width of the current Rectangle instance by adding the Height and Width in the rect parameter. The result of the method is a new Rectangle with the added properties.

Calling a struct Method

You can call the Add method, from Listing 12-5, through an instance of a Rectangle struct. Listing 12-6 shows how to instantiate twoRectangle structs, call the Add method and assign the result of the Add method call to another Rectangle struct.

Listing 12-6: Calling a struct Method
using System;

/// <summary>
/// Example of declaring and using
    a struct
/// </summary>
class StructExample
{
    /// <summary>
    /// Entry point: execution starts
        here
    /// </summary>
    static void Main()
    {
        // instantiate a new Rectangle struct
        // where Width is set to 1 and Height is set to 3
	Rectangle rect1 = new Rectangle();
        rect1.Width = 1;
        rect1.Height = 3;

        // show the value of Width and Height for rect1
        Console.WriteLine("rect1: {0}:{1}", rect1.Width, rect1.Height);

        // instantiate a new Rectangle struct
        // where Width is set to 5 and Height is set to 7
	Rectangle rect2 = new Rectangle(5, 7);

        // show the value of Width and Height for rect2
        Console.WriteLine("rect2: {0}:{1}", rect2.Width, rect2.Height);


        // invoke the Add method on the rect1 Rectangle struct instance,
        // passing the rect2 Rectangle struct instance as an argument
        // and assigning the new copy of the value returned by the
        // Add method to the rect3 Rectangle struct.
        Rectangle rect3 = rect1.Add(rect2);

        // show the value of Width and Height for rect3
        Console.WriteLine("rect3: {0}:{1}", rect3.Width, rect3.Height);

        Console.ReadKey();
   }
}

In the Main method of Listing 12-6, the code instantiates rect1 and rect2, which are both Rectangle structs. They assign values to their Height and Width properties. The struct instantiation examples should be familiar by now because they are the same as earlier examples. What’s useful about Listing 12-6 is the highlighted code, which shows how to invoke the Add method of the Rectangle struct. The code invokes the Add method of the rect1 instance and passes rect2 as the Rectangle struct to be added to rect1. The Add method in Listing 12-5 shows what happens when this code executes. In Listing 12-6, the return value of the Add method is assigned to rect3. That is a larger Rectangle with each of its sides equal to the sum of the individual sides of rect1 and rect2. Here’s the output:

rect1: 1:3 
rect2: 5:7 
rect3: 6:10

Final Thoughts About C# Struct

This lesson described a C# struct and identified a few differences between class and struct types. You can instantiate a struct either via a default constructor or a custom constructor overload that you write. The other thing you also learned was how to implement properties and methods in structs.

I invite you to return for Lesson 13: Interfaces.

C# Data Types: Operators and Variables in Lesson 2

This lesson introduces C# data types, operators and variables. Its goal is to meet the following objectives:

  • Understand what a variable is.
  • Familiarization with C# built-in types.
  • Get an introduction to C# operators.
  • Learn how to use Arrays.

Variables and Types

“Variables” are simply storage locations for data. You can place data into them and retrieve their contents as part of a C# expression. The interpretation of the data in a variable is controlled through “Types”.

C# is a “Strongly Typed” language. Thus all operations on variables are performed with consideration of what the variable’s “Type” is. There are rules that define what operations are legal to maintain the integrity of the data you put in a variable.

The C# simple types consist of the Boolean type and three numeric types – Integrals, Floating Point, Decimal, and String. The term “Integrals”, which is defined in the C# Programming Language Specification, refers to the classification of types that include sbyte, byte, short, ushort, int, uint, long, ulong, and char. More details are available in the Integral Types section later in this lesson. The term “Floating Point” refers to the float and double types, which are discussed, along with the decimal type, in more detail in the Floating Point and Decimal Types section. The string type represents a string of characters and is discussed in The String Type section, later in this lesson. The next section introduces the boolean type.

The Boolean Type

Boolean types are declared using the keyword, bool. They have two values: true or false. In other languages, such as C and C++, boolean conditions can be satisfied where 0 means false and anything else means true. However, in C# the only values that satisfy a boolean condition is true and false, which are official keywords. Listing 2-1 shows one of the many ways that boolean types can be used in a program.

Listing 2-1. Displaying Boolean Values: Boolean.cs
using System;

class Booleans
{
    public static void Main()
    {
        bool content = true;
        bool noContent = false;

        Console.WriteLine("It is {0} that C# Station provides C# programming language content.", content);
        Console.WriteLine("The statement above is not {0}.", noContent);
    }
}

In Listing 2-1, the boolean values are written to the console as part of a sentence. The only legal values for the bool type are either true or false, as shown by the assignment of true to content and false to noContent. When run, this program produces the following output:

It is True that C# Station provides C# programming language content.
The statement above is not False.

Integral Types

In C#, an integral is a category of types. For anyone confused because the word Integral sounds like a mathematical term, from the perspective of C# programming, these are defined as Integral types in the C# programming language specification. They are whole numbers, either signed or unsigned, and the char type. The char type is a Unicode character, as defined by the Unicode Standard. Table 2-1 shows the integral types, their size, and range.

Table 2-1. The Size and Range of C# Integral Types
Type Size (in bits) Range
sbyte 8 -128 to 127
byte 8 0 to 255
short 16 -32768 to 32767
ushort 16 0 to 65535
int 32 -2147483648 to 2147483647
uint 32 0 to 4294967295
long 64 -9223372036854775808 to 9223372036854775807
ulong 64 0 to 18446744073709551615
char 16 0 to 65535

Integral types are well suited for those operations involving whole number calculations. The char type is the exception, representing a single Unicode character. As you can see from the table above, you have a wide range of options to choose from, depending on your requirements.

Floating Point and Decimal Types

A C# floating point type is either a float or double. They are used any time you need to represent a real number, as defined by IEEE 754. For more information on IEEE 754, visit the IEEE Web Site. Decimal types should be used when representing financial or money values. Table 2-2 shows the floating point and decimal types, their size, precision, and range.

Table 2-2. The Floating Point and Decimal Types with Size, precision, and Range
Type Size (in bits) precision Range
float 32 7 digits 1.5 x 10-45 to 3.4 x 1038
double 64 15-16 digits 5.0 x 10-324 to 1.7 x 10308
decimal 128 28-29 decimal places 1.0 x 10-28 to 7.9 x 1028

Floating point types are used when you need to perform operations requiring fractional representations. However, for financial calculations, the decimal type is the best choice because you can avoid rounding errors.

The String Type

A string is a sequence of text characters. You typically create a string with a string literal, enclosed in quotes: “This is an example of a string.” You’ve seen strings being used in Lesson 1, where we used the Console.WriteLine method to send output to the console.

Some characters aren’t printable, but you still need to use them in strings. Therefore, C# has a special syntax where characters can be escaped to represent non-printable characters. For example, it is common to use newlines in the text, which is represented by the ‘\n’ char. The backslash, ‘\’, represents the escape. When preceded by the escape character, the ‘n’ is no longer interpreted as an alphabetical character but now represents a newline.

You may be wondering how you could represent a backslash character in your code. We have to escape that too by typing two backslashes, as in ‘\\’. Table 2-3 shows a list of common escape sequences.

Table 2-3. C# Character Escape Sequences
Escape Sequence Meaning
\’ Single Quote
\” Double Quote
\\ Backslash
\0 Null, not the same as the C# null value
\a Bell
\b Backspace
\f form Feed
\n Newline
\r Carriage Return
\t Horizontal Tab
\v Vertical Tab

Another useful feature of C# strings is the verbatim literal, which is a string with a @ symbol prefix, as in @”Some string”. Verbatim literals make escape sequences translate as normal characters to enhance readability. To appreciate the value of verbatim literals, consider a path statement such as “c:\\topdir\\subdir\\subdir\\myapp.exe”. As you can see, the backslashes are escaped, causing the string to be less readable. You can improve the string with a verbatim literal, like this: @”c:\topdir\subdir\subdir\myapp.exe”.

That is fine, but you have the problem where quoting text is not as easy. In that case, you would specify double quotes. For example, the string “copy \”c:\\source file name with spaces.txt\” c:\\newfilename.txt” would be written as the verbatim literal @”copy “”c:\source file name with spaces.txt”” c:\newfilename.txt”.

C# Operators

Results are computed by building expressions. These expressions are built by combining variables and operators together into statements. The following table describes the allowable operators, their precedence, and associativity.

Table 2-4. Operators with their precedence and Associativity
Category (by precedence) Operator(s) Associativity
Primary x.y  f(x)  a[x]  x++  x–  new  typeof  default  checked  unchecked delegate left
Unary +  –  !  ~  ++x  –x  (T)x right
Multiplicative *  /  % left
Additive +  – left
Shift <<  >> left
Relational <  >  <=  >=  is as left
Equality ==  != right
Logical AND & left
Logical XOR ^ left
Logical OR | left
Conditional AND && left
Conditional OR || left
Null Coalescing ?? left
Ternary ?: right
Assignment =  *=  /=  %=  +=  -=  <<=  >>=  &=  ^=  |=  => right

Left associativity means that operations are evaluated from left to right. Right associativity means all operations occur from right to left, such as assignment operators where everything to the right is evaluated before the result is placed into the variable on the left.

Most operators are either unary or binary. Unary operators form expressions on a single variable, but binary operators form expressions with two variables. Listing 2-2 demonstrates how unary operators are used.

Listing 2-2. Unary Operators: Unary.cs
using System;

class Unary
{
    public static void Main()
    {
        int unary = 0;
        int preIncrement;
        int preDecrement;
        int postIncrement;
        int postDecrement;
        int positive;
        int negative;
        sbyte bitNot;
        bool logNot;

        preIncrement = ++unary;
        Console.WriteLine("pre-Increment: {0}", preIncrement);

        preDecrement = --unary;
        Console.WriteLine("pre-Decrement: {0}", preDecrement);

        postDecrement = unary--;
        Console.WriteLine("Post-Decrement: {0}", postDecrement);

        postIncrement = unary++;
        Console.WriteLine("Post-Increment: {0}", postIncrement);

        Console.WriteLine("Final Value of Unary: {0}", unary);

        positive = -postIncrement;
        Console.WriteLine("Positive: {0}", positive);

        negative = +postIncrement;
        Console.WriteLine("Negative: {0}", negative);

        bitNot = 0;
        bitNot = (sbyte)(~bitNot);
        Console.WriteLine("Bitwise Not: {0}", bitNot);

        logNot = false;
        logNot = !logNot;
        Console.WriteLine("Logical Not: {0}", logNot);
    }
}

When evaluating expressions, post-increment (x++) and post-decrement (x–) operators return their current value and then apply the operators. However, when using pre-increment (++x) and pre-decrement (–x) operators, the operator is applied to the variable prior to returning the final value.

In Listing 2-2, the unary variable is initialized to zero. When the pre-increment (++x) operator is used, unary is incremented to 1 and the value 1 is assigned to the preIncrement variable. The pre-decrement (–x) operator turns unary back to a 0 and then assigns the value to the pre Decrement variable.

When the post-decrement (x–) operator is used, the value of unary, 0, is placed into the post Decrement variable and then unary is decremented to -1. Next, the post-increment (x++) operator moves the current value of unary, -1, to the post Increment variable and then increments unary to 0.

The variable bitNot is initialized to 0 and the bitwise not (~) operator is applied. The bitwise not (~) operator flips the bits in the variable. In this case, the binary representation of 0, “00000000”, was transformed into -1, “11111111”.

While the (~) operator works by flipping bits, the logical negation operator (!) is a logical operator that works on bool values, changing true to false or false to true. In the case of the logNot variable in Listing 2-2, the value is initialized to false, and the next line applies the logical negation operator, (!), which returns true and reassigns the new value, true, to logNot. Essentially, it is toggling the value of the bool variable, logNot.

Furthermore

The setting of positive is a little tricky. At the time that it is set, the postIncrement variable is equal to -1. Applying the minus (-) operator to a negative number results in a positive number, meaning that positive will equal 1, instead of -1. The minus operator (-), which is not the same as the pre-decrement operator (–), doesn’t change the value of postInc – it applies a sign negation. The plus operator (+)doesn’t affect the value of a number, assigning negative with the same value as postIncrement, -1.

Notice the expression (sbyte)(~bitNot). Any operation performed on types sbyte, byte, short, or ushort return int values. To assign the result into the bitNot variable we had to use a cast, (Type), operator, where Type is the type you wish to convert to (in this case – sbyte). The cast operator is shown as the Unary operator, (T)x, in table 2-4. Cast operators must be performed explicitly when you go from a larger type to a smaller type because of the potential for lost data. Generally speaking, assigning a smaller type to a larger type is no problem since the larger type has room to hold the entire value. Also, be aware of the dangers of casting between signed and unsigned types. You want to be sure to preserve the integrity of your data. Many basic programming texts contain good descriptions of bit representations of variables and the dangers of explicit casting.

Here’s the output from the Listing 2-2:

pre-Increment: 1
pre-Decrement 0
Post-Decrement: 0
Post-Increment: -1
Final Value of Unary: 0
Positive: 1
Negative: -1
Bitwise Not: -1
Logical Not: true

In addition to unary operators, C# has binary operators that form expressions of two variables. Listing 2-3 shows how to use the binary operators.

Listing 2-3. Binary Operators: Binary.cs
using System;

class Binary
{
    public static void Main()
    {
        int x, y, result;
        float floatresult;

        x = 7;
        y = 5;

        result = x+y;
        Console.WriteLine("x+y: {0}", result);

        result = x-y;
        Console.WriteLine("x-y: {0}", result);

        result = x*y;
        Console.WriteLine("x*y: {0}", result);

        result = x/y;
        Console.WriteLine("x/y: {0}", result);

        floatresult = (float)x/(float)y;
        Console.WriteLine("x/y: {0}", floatresult);

        result = x%y;
        Console.WriteLine("x%y: {0}", result);

        result += x;
        Console.WriteLine("result+=x: {0}", result);
    }
}

And here’s the output:

x+y: 12
x-y: 2
x*y: 35
x/y: 1
x/y: 1.4
x%y: 2
result+=x: 9

Listing 2-3 shows several examples of binary operators. As you might expect, the results of addition (+), subtraction (-), multiplication (*), and division (/) produce the expected mathematical results.

The floatresult variable is a floating point type. We explicitly cast the integer variables x and y to calculate a floating point value.

There is also an example of the remainder(%) operator. It performs a division operation on two values and returns the remainder.

The last statement shows another form of the assignment with the operation (+=) operator. Any time you use the assignment with operation operator, it is the same as applying the binary operator to the left hand and right-hand sides of the operator and putting the results into the left-hand side. The example could have been written as result = result + x; and returned the same value.

The Array Type

Another data type is the Array, which can be thought of as a container that has a list of storage locations for a specified type. When declaring an Array, specify the type, name, dimensions, and size.

Listing 2-4. Array Operations: Array.cs
using System;

class Array
{
    public static void Main()
    {
        int[] myInts = { 5, 10, 15 };
        bool[][] myBools = new bool[2][];
        myBools[0] = new bool[2];
        myBools[1] = new bool[1];
        double[,] myDoubles = new double[2, 2];
        string[] myStrings = new string[3];

        Console.WriteLine("myInts[0]: {0}, myInts[1]: {1}, myInts[2]: {2}", myInts[0], myInts[1], myInts[2]);

        myBools[0][0] = true;
        myBools[0][1] = false;
        myBools[1][0] = true;
        Console.WriteLine("myBools[0][0]: {0}, myBools[1][0]: {1}", myBools[0][0], myBools[1][0]);

        myDoubles[0, 0] = 3.147;
        myDoubles[0, 1] = 7.157;
        myDoubles[1, 1] = 2.117;
        myDoubles[1, 0] = 56.00138917;
        Console.WriteLine("myDoubles[0, 0]: {0}, myDoubles[1, 0]: {1}", myDoubles[0, 0], myDoubles[1, 0]);

        myStrings[0] = "Joe";
        myStrings[1] = "Matt";
        myStrings[2] = "Robert";
        Console.WriteLine("myStrings[0]: {0}, myStrings[1]: {1}, myStrings[2]: {2}", myStrings[0], myStrings[1], myStrings[2]);

    }
}

And here’s the output:

myInts[0]: 5, myInts[1]: 10, myInts[2]: 15
myBools[0][0]: true, myBools[1][0]: true
myDoubles[0, 0]: 3.147, myDoubles[1, 0]: 56.00138917
myStrings[0]: Joe, myStrings[1]: Matt, myStrings[2]: Robert

Listing 2-4 shows different implementations of Arrays. The first example is the myInts Array, which is a single-dimension array. It is initialized at declaration time with explicit values.

Next is a jagged array, myBools. It is essentially an array of arrays. We needed to use the new operator to instantiate the size of the primary array and then use the new operator again for each sub-array.

The third example is a two-dimensional array, myDoubles. Arrays can be multi-dimensional, with each dimension separated by a comma. It must also be instantiated with the new operator.

Furthermore

One of the differences between jagged arrays, myBools[][], and multi-dimension arrays, myDoubles[,], is that a multi-dimension array will allocate memory for every element of each dimension. A jagged array will only allocate memory for the size of each array in each dimension that you define. Most of the time, you’ll be using multi-dimension arrays, if you need multiple dimensions. You will only use jagged arrays in very special circumstances when you can save significant memory by explicitly specifying the sizes of the arrays in each dimension.

Finally, we have the single-dimensional array of string types, myStrings.

In each case, you can see those array elements are accessed by identifying the integer index for the item you wish to refer to. Array sizes can be an int type value. Their indexes begin at 0.

To Wrap Up C# Data Types: Operators and Variables

A variable is an identifier with a type that holds a value of that type. Simple types include the integrals, floating points, decimal, and bool. C# has several mathematical and logical operators that participate in forming expressions. C# also offers the single dimension, multi-dimension and jagged array types.

Integer data type enables a variable to store numeric values. Character data type enables a variable to store only one character. Floating point data type consists of a float and a double. A float enables a variable to store decimal values.  Double data type permits up to 10 digits after the decimal. 

In this lesson, you learned how to write simple statements and code a program that works linearly from start to finish. However, this is not as useful as it can be because you need to be able to make decisions and execute different blocks of code depending on different conditions. I invite you to return for C# If Statement: Control Selection in Lesson 3, where you can learn how to branch your logic for more powerful decision making.

Follow Joe Mayo on Twitter.

We hope that this lesson on C# Data Types was informative. Please email us with your feedback.