Web Design that taps into the haromny and vision of your dreams.

Advanced C# Programming Topics

By on in Coding

3,334 words, estimated reading time 16 minutes.

Introduction to Programming with C# Series
  1. Introduction to Programming with C# 7
  2. C# Programming Fundamentals
  3. Introduction to Object-Oriented Programming
  4. C# Object-Oriented Programming Part 2
  5. Flow Control and Control Structures in C#
  6. C# Data Types, Variables and Casting
  7. C# Collection Types (Array, List, Dictionary, Hash Table)
  8. C# Operators
  9. Using Data in C# 7 with ADO.Net & Entity Framework
  10. LINQ: .NET Language Integrated Query
  11. Error and Exception Handling in C#
  12. Advanced C# Programming Topics
  13. Reflection in C#
  14. What Are ASP.Net Webforms
  15. Introduction to ASP.Net MVC
  16. Windows Application Development
  17. Assemblies in C#
  18. Working with Resources Files, Culture & Regions
  19. Regular Expressions in C#
  20. Introduction to XML with C#
  21. Complete Guide to File Handling in C#

Don’t let the word "advanced" scare you any longer. Here we show you that, with just a bit of guidance and some very simple examples, even newcomers to the world of C# are capable of incorporating advanced features and techniques into their day-to-day activities.

Structs

Structs are very similar to classes, in fact from a syntax and declaration point of view they are the same. The main difference is that classes are reference types while structs are value types.

For a class, this means that you are working with a reference to a memory location, so when you pass a class into a function, you are actually passing in a memory location. The function then works with the same class values in the same memory area.

A struct, on the other hand, is a value type, which means that you are working with the values directly. When you pass a struct into a function you are passing a copy of the values.

We can see this in action in this short test program. It will hopefully make things a little clearer.

using System;
 
class TestClass
{
    public string testString;
}
 
struct TestStruct
{
    public string testString;
}
 
class StructVsClass
{
    static void Main()
    {
        // Create an instance of each type
        TestClass instanceOfClass = new TestClass();
        TestStruct instanceOfStruct = new TestStruct();
 
        // Initialise testString parameter
        instanceOfClass.testString = "This is a test";
        instanceOfStruct.testString = "This is a test";
 
        // Call method to change test on each type
        ChangeTestClass(instanceOfClass);
        ChangeTestStruct(instanceOfStruct);
 
        // Output results
        Console.WriteLine("Class: " + instanceOfClass.testString);
        Console.WriteLine("Struct: " + instanceOfStruct.testString);
    }
 
    public static void ChangeTestClass(TestClass test)
    {
        test.testString = "Changed Text from ChangeTestClass()";
    }
 
    public static void ChangeTestStruct(TestStruct test)
    {
        test.testString = "Changed Text from TestStruct()";
    }
}
 

Starting off, we have a TestClass and a TestStruct, which as you can see are the same. In the Main method, we create an instance of each and set the value of both to "This is a test", then call a method to change the value of the text. In each case, we pass in the class or the struct and set the text to "Changed Text from Change...Class()". Finally, output the text value to the console.

What would you expect the results to show?

Class: Changed Text from ChangeTestClass()
Struct: This is a test
Press any key to continue . . .

So from this, we can see that the class has had its value changed, but the struct hasn't. Why is this?

Well, we passed the class to the function as a reference type, meaning that we actually passed a memory location. The method used this memory location to change the text, so when the original class accessed the data again it's looking at the same location and therefore the new text.

When we passed in the struct, we passed a copy of the data to the method, which then changed the copy. The original struct and its data were unaffected.

Extension Methods

Extension Methods allow you to extend an existing type with new functionality, without having to sub-class or recompile the old type.

The usual approach would have been to define a function and then call it each time, and over time you get more and more of these functions and they get bundled together in a utility class.

With Extension Methods, you can actually extend the class in question to support this directly.

Let's say you need to convert a string to an integer. You could call int.TryParse many times, you could create a utility function, or you can create an extension method.

int.TryParse Code

var result = 0;
var input = "123";
int.TryParse(input, out result);
if (result > 0)
{
...

Utility Class Function

 
public static class Utils
{
  public static int StringToInt(string input)
  {
    var result = 0;
    int.TryParse(input, out result);
    return num;
  }
}
 
public class Program
{
  public Main()
  {
    var number = Utils.StringToInt("123");
  }
}

Extension Method

public static class Extensions
{
  public static int ToInt(this String input)
  {
    int result;
    if (!int.TryParse(input, out result))
    {
      result = 0;
    }
    return result;
  }
}
 
public class Program
{
  public Main()
  {
    var input = "123";
    var number = input.ToInt();
  }
}

Extension methods are a quick way to write reusable code without writing a bunch of utility classes. It also gives a clear understanding of what the code does through the name.

Events & Delegates

Delegates allow your code to dynamically change the method that it calls. This allows a method to change, based on the code that invokes it. Events are delegate methods that are invoked on a certain condition.

Delegates

A delegate type is a reference type, which allows an indirect call to a method. It holds a pointer to a method declared elsewhere, and when the delegate is invoked, it, in turn, invokes the method it points to. Delegate methods are very similar to C++ function pointers, except that a delegate type can point to multiple methods.

In this example we call an OutputText method with a string and an output method and the delegate type, Write, sorts out whether to write to a file or write to the screen.

using System
using System.IO
 
public class Program
{
 
  // Define our delegate type
  public delegate void Write(string theText);
 
  // Method for output to screen
  public static void OutputToScreen(string theText)
  {
    Console.WriteLine(theText);
  }
 
  // Method to output to file
  public static void WriteToFile(string theText)
  {
    StreamWriter fileWriter = File.CreateText("c:/delegate-demo.txt");
    fileWriter.WriteLine(theText);
    fileWriter.Flush();
    fileWriter.Close();
  }
 
  public static void Main()
  {
    // Assign a method to a delegate
    Write toFile = new Write(WriteToFile);
    Write toScreen = new Write(OutputToScreen);
 
    Display("This is a delegate demo", toFile);
    Display("This is a delegate demo", toScreen);
  }
 
  public static void Display(string theText, Write outputMethod)
  {
    outputMethod(theText);
  }
}

A bit of a long example, but it is good to illustrate how the delegate can be used to determine the method to call for outputting the text, either to file or to screen.

We start off by defining the delegate in the same way, as we would start to declare a method. We use the delegate keyword to mark the method, and because there are no implementations we terminate the statement with a semi-colon.

Next, we define two methods, which could have the delegate pointing to. They must have the same method signature as the delegate defined above.

In the main method we assign an instance of the delegate type to each of our test output methods, then we call a generic Display method with the text to process and the display method.

This Display method invokes the output message with the text passed in.

Multicast Delegates

A multicast delegate can call to multiple methods using the += operator.

  public static void Main()
  {
    // Assign a method to a delegate
    Write toFile = new Write(WriteToFile);
    toFile += new Write(OutputToScreen);

In this example, when toFile is invoked, both WriteToFile and OutputToScreen will be called.

You can remove delegates using the -= operator:

  public static void Main()
  {
    // Assign a method to a delegate
    Write toFile = new Write(WriteToFile);
    toFile += new Write(OutputToScreen);
    toFile -= WriteToFile;
 

When invoking toFile, it will only call OutputToScreen now.

Events

Events are similar to delegates, except that a delegate has to be called, whereas an event is a response to a condition. Examples of common events are Button_Click, Page_Load and Mouse_Over. Events can also be multicast, assigned to and removed in the same way as a multicast delegate.

this.Load += new System.EventHandler(this.Form1_Load);

The concept of events is based on a subscription-notification model. Any class must be able to "subscribe" to an event and receive a "notification" whenever the events occur. When a class subscribes to an event, it declares its interest in the event, and when it receives the notification it has its delegate methods run.

In the following example shows how testClass2 subscribes to an event issued by testClass1.

  • testClass1 issues events. It has a multicast delegate, eventD.
  • testClass2 subscribes to the event with its method eventHandler by adding a reference to eventD.
  • When testClass1 issues an event it invokes eventD, which causes eventHandler to fire.

Creating Events

You can create events in your code in a similar way that properties are declared - by defining a public event and a protected method.

public class Program
{
  public delegate void EventDelegate(object sender, EventArgs args);
  public event EventDelegate testEvent;
 
  protected virtual void onTestEvent(EventArgs args)
  {
    if (testEvent != null)
      testEvent(this, args)
  }
}

You should always check that the delegate is not null before calling it, as you could end up with exceptions being thrown.

Events and Inheritance

If your class inherits from a class, which already issues or responds to events, you can intercept these actions by overriding the method used to raise it.

protected override void onMyEvent(EventArgs args)
{
  Console.WriteLine("The Event Stops Here");
}

This will also prevent any "subscriptions" from receiving their "notifications". If you wish subscribers to continue to receive the notifications then simply call the base event method.

protected override void onMyEvent(EventArgs args)
{
  Console.WriteLine("The Event Passed Here and Carried on");
  base.onMyEvent(args);
}

Unsafe Code Execution

C# and any other .Net language is a managed environment. This means that, among other things, in the background, a hidden process called the Garbage Collector cleans up unused memory and old references to make room for new.

Managed code is the direct responsibility of the .Net CLR which looks over things like memory management. The problem with managed code is the garbage collector process that removes unused memory locations and references and will cause other pointers to change reference as a result. For this reason pointers are only allowed within the unsafe code block.

By marking a section of code as "unsafe" you take away this responsibility from the CLR to the programmer. It is now the coder's responsibility to clean up objects and memory after they are used. The advantage of this method is that code is much faster to execute as a lot of the overhead of managed code is eliminated.

Defining Unsafe Code

You are required to mark code as "unsafe" to prevent accidental use of pointers (especially from C++ programmers). Unsafe code can be marked by simply enclosing a section of code in a unsafe block:

...
unsafe
{
...
// this code is unsafe - pointers can be used.
...
}
...

Unsafe can also be used as an access modifier when declaring methods:

unsafe public static void myUnsafeMethod
{
...
// this code is unsafe - pointers can be used.
...
}

Pointer Notation

As well as specifying blocks of code as unsafe, you must also tell the compiler that you want to allow unsafe code blocks to be executed. This is done from within the project options. Under the Project menu select Properties and under the Build tab there is an option to "Allow unsafe code blocks". Once this is set and you are within an unsafe code block you can use pointers. This should give you an indication of how dangerous pointers are.

Pointers are declared in the same way as in C++.

int normalInteger;
int *pointerInteger;

Note the use of an asterisk before the variable name. In this example, normalInteger is assigned the value of an integer (default = 0) , whereas pointerInteger is assigned the memory location of an integer. The asterisk can also be placed after the type, e.g. int* pointerInteger.

What is a Pointer?

A normal variable is a container that holds information of a particular type (int, string, enum and so on). A pointer variable does not hold a value or a type, but instead, it contains a memory location that points to the container. When you read and write to a pointer variable you are getting and setting the memory location (also known as an address).

Pointers and Memory Locations in C#
Pointers and Memory Locations in C#

In the above diagram, we can see 5 memory locations, each with a type and a value. The size and length of the memory location will vary depending on the size of the type used, for example, an integer is a 4-byte value type, so each memory location of type int is 4 bytes long.

If we declare a standard variable, e.g. int myInt = 52432, then the container identified as myInt contains the value 52432. This value cannot be changed, so we say that it is an immutable type. When you re-assign a value to it later, e.g. myInt = 89343, another int type is created and myInto points to that value. This is true for all value types.

When we declare a pointer to an int, e.g. int *myInt, we are assigning the container myInt the memory location of 4 available bytes in memory, in this case, 5. In reality, there are many hundreds of thousands of memory addresses which are represented in hex. For simplicity, we are just using simple numbers.

If you were to assign a value to a pointer type, e.g.

int *myInt;
myInt = 2;

Then you are assigning a different memory location to the variable. In this example, myInt now points to memory location 2, which is a string. Because integers are 4 bytes long, when you try and access the value of the pointer, it will only return the first 4 bytes of the string. If you assign to the pointer then the original string will become corrupt.

By changing pointers around it is very easy to corrupt or overwrite data in memory and cause the operating system to error or even crash. This is why pointers are dangerous and should only be used when it is essential to use them. There are valid reasons for using pointers, for example, applications which feature large numbers of complex mathematical operations where speed is paramount - i.e. a device driver.

Basic usage of Pointers

Pointers are used slightly different to normal variables. If you use a pointer as you would a variable then you will more than likely cause memory corruption.

unsafe
{
  int* x;
  int y = 10;
  x = &y;  
  Console.WriteLine("Value of x is: " + *x);
}

Value of x is: 10

Fixed Keyword

The Garbage Collector will re-arrange memory locations as it cleans up, causing a problem with pointers to objects being moved but the pointer still points to the old location. To overcome this problem there is a fixed keyword that can be used to pin the object and makes sure that the garbage collector will not relocate the object.

Color cl = new Color();
unsafe
{
  Console.WriteLine("Red Component of Color is: {0}", cl.R);
  fixed (byte *pRed = &cl.R)
  {
    *pRed = 122;
  }
  Console.WriteLine("Red Component of Color is: {0}", cl.R);
}

Indexers

Indexers, also known as Iterators or Smart Arrays, are very similar to properties, but they allow you to use an index on an object to obtain values. Instead of creating a property name and public/private fields you simply use the this keyword to reference the object itself. Indexers allow you to access data within a class or object by treating it like an array.

class demoClass
{
 private string[] data = new string[5];
 public string this [int index]
 {
  get
  {
   return data[index];
  }
  set
  {
   data[index] = value;
  }
 }
}

In this short example public string this defines the indexer with an integer index. When accessed the code in the get or set executes in the same way as a property.

demoClass sample = new demoClass()
 
sample[0] = "This";
sample[1] = "Is";
sample[2] = "a";
sample[3] = "Indexer";
 
Console.WriteLine(sample[0]);
Console.WriteLine(sample[1]);
Console.WriteLine(sample[2]);
Console.WriteLine(sample[3]);

Attributes

Method Attributes provide a means of changing the behaviour of a method or class. You can set up conditional flags, mark a method as a web method, obsolete or allow a class to be serialized to XML.

Attributes are specified in square brackets before the class or methods that they are to apply to.

[WebMethod]
public string getPageTitle
{
  return "Test Code";
}

WebMethod and WebService

Let's first look at the web method, as it was used in the above example. In Web Services, all methods are all hidden from direct access to the Internet. You need to specify, using the WebMethod attribute, any method you want to expose to the Internet.

public string HelloWorld()
{
  return "Hello World";
}
 
[WebMethod]
public string web_HelloWorld()
{
  return "Hello World";
}

In the above example, anybody connecting to the web service will only be able to invoke the web_HelloWorld method even though the regular HelloWorld is marked as public.

You must also mark the class as a web service using the WebService attribute. This attribute accepts parameters in the form of a namespace.

[WebService(Namespace="http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{ ...

In the above example, you can see how to apply more than one attribute to a class.

Serializable

When you mark an object as [Serializable] you are informing the compiler that at some point you want to save the object as XML data stream. You can also implement the ISerializable interface.

To serialise an object you can use an XML Serializer and StreamWriter to write to a file.

using System;
using System.Xml.Serialization;
using System.IO;
 
[Serializable]
public class myTestClass
{
  public string myString = "Hello World";
  public int myInt = 1234;
  public string[] myArray = new string[4];
 
  public string myMethod()
  {
    return "Hello World";
  }
}
 
class Program
{
  static void Main()
  {
    myTestClass test = new myTestClass();
    test.myArray[0] = "qwerty";
    test.myArray[1] = "asdfgh";
    test.myArray[2] = "zxcvbn";
    test.myArray[3] = "123456";
 
    XmlSerializer mySerializer = new XmlSerializer(typeof(myTestClass));
    StreamWriter myWriter = new StreamWriter("c:/mtTestClass.xml");
    mySerializer.Serialize(myWriter, test);
    myWriter.Close();
  }
}

When run, this will create an XML document containing the current data of the object.

<?xml version="1.0" encoding="utf-8"?>
<myTestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <myString>Hello World</myString>
  <myInt>1234</myInt>
  <myArray>
    <string>qwerty</string>
    <string>asdfgh</string>
    <string>zxcvbn</string>
    <string>123456</string>
  </myArray>
</myTestClass>

Deserialize Objects

Deserialization is the opposite of serialization, reading the data from XML and converting it into an object. The process is very similar to the serialization method above.

static void Main()
{
  myTestClass test;
 
  XmlSerializer mySerializer = new XmlSerializer(typeof(myTestClass));
  FileStream myFileStream = new FileStream("c:/mtTestClass.xml",FileMode.Open);
 
  test = (myTestClass)mySerializer.Deserialize(myFileStream);
 
  // Just to prove it works
  Console.WriteLine(test.myArray[0]);
}

Conditional Attributes

The conditional attribute can be set on methods so that they can be called only if that condition is true. An example of this is a debug log. When the program is compiled for a debug build, the method will run each time it is called, however when compiled for release the method will never be called. The method is still compiled into the assembly regardless of the target build.

class Program
{
  [Conditional("DEBUG")]
  static void debugMethod(string message)
  {
    Console.WriteLine(message);
  }
 
  static void Main()
  {
    Console.WriteLine("This is a test");
    debugMethod("This is debug message");
    Console.WriteLine("This is a test");
  }
}

When the program is compiled for debugging the console window will show:

This is a test
This is a debug message
This is a test

When run, for a release build the console window will show:

This is a test
This is a test

Obsolete

The obsolete attribute is used to mark a method that should not be used any more. It allows for backward compatibility when using assemblies.

[Obsolete("This class is obsolete, please use testClass instead.")]
class oldTestClass
{
  public string Hello()
  {
    return "Hello";
  }
}
 
class testClass
{
  public string Hello()
  {
    return "Hello World";
  }
}
 
class Program
{
  static void Main()
  {
    oldTestClass test = new oldTestClass();
    test.Hello();
  }
}

This will display a warning message at compile time with the obsolete message. It will not stop any other code from using the method, or break any existing code; it will only show the message to developers.

Last updated on: Thursday 11th October 2018

 

Comments

Have a question or suggestion? Please leave a comment to start the discussion.

 

Leave a Reply

Please keep in mind that all comments are moderated according to our privacy policy, and all links are nofollow. Do NOT use keywords in the name field. Let's have a personal and meaningful conversation.

Your email address will not be published.