C# Tutorial

Silan Liu

 

1.          Basics and Individual Topics. 2

1.1.       Generating Schema, Class and DataSet from XML Using xsd.exe 2

1.2.       Asynchronous Programming with Delegates 2

1.3.       Event 3

1.4.       Convert Between Objects & byte Array with BinaryFormatter 4

1.5.       Encrypting and Decrypting with DES Crypto Service Provider 5

 

1.      Basics and Individual Topics

1.1.        Generating Schema, Class and DataSet from XML Using xsd.exe

To generate a schema definition file (xsd) from XML file orders.xml:

xsd orders.xml

A file called orders.xsd will be generated.

To generate a class definition from the schema definition:

xsd orders.xsd /classes /language:cs

A file called orders.cs containing a class definition in c# will be generated.

To generate a class definition of a strongly typed DataSet from the schema definition:

xsd orders.xsd /dataset /language:cs

1.2.        Asynchronous Programming with Delegates

Delegate is a .NET version of function pointer, which can be used for callback purpose. Moreover, compiler generates some methods and properties for the delegate, including methods BeginInvoke and EndInvoke, so that we can call the wrapped method asynchronously.

Now suppose there is the following time-consuming method that needs to be called asynchronously:

private int Calc(int input1, int input2, ref string strResult)

{

    System.Threading.Thread.Sleep(new System.TimeSpan(0, 0, 0, 5, 0));

    strResult = input1 + " + " + input2 + " = " + (input1 + input2);

    return input1 + input2;

}

The application UI is as follows:

 

When we press the “Calculate” button, the two integers in the first two textboxes are used to call method Calc, the returned integer result is put in the third textbox, and a string representation is passed back through Calc’s ref parameter, and is put in the message textbox.

The bottom textbox and button has nothing to do with the delegate process and is only used to check that the UI is not locked before the method returns.

An asynchronous method invoking process would be:

1.       Define a delegate type with the same signature as the method to call;

 private delegate int CalcDelegate(int input1, int input2, ref string strResult); 

2.       Define a callback method which will be called when the dispatched thread finishes the time-consuming method call and returns. The content of the callback method will be discussed later.

private void CalcCallback(IAsyncResult ar)

{

    ...

}

3.       To initiate an asynchronous call, create an instance of the delegate type to wrap the time-consuming method, and call the delegate’s BeginInvoke method with all parameters needed by the time-consuming method:

CalcDelegate mDeleg;

 

private void btnCalc_Click(object sender, System.EventArgs e)

{

    mDeleg = new CalcDelegate(Calc);

    AsyncCallback callback = new AsyncCallback(CalcCallback);

 

    int input1 = Convert.ToInt16(tbInput1.Text);

    int input2 = Convert.ToInt16(tbInput2.Text);

    string strResult = null; // dummy parameter

 

    deleg.BeginInvoke(input1, input2, ref strResult, callback, 123456789);

}

When BeginInvoke is called, a separate thread is dispatched to call the time-consuming method. The last parameter of BeginInvoke is of type object and can be any object that the caller method wants to pass to the callback method. It is simply passed to the thread and passed back to the callback method as the IAsyncResult.AsyncState property.

4.       When the wrapped method returns, the thread will call the callback method with an IAsyncResult parameter. In the callback method, we are supposed to acquire a handle to the delegate contained in the IAsyncResult, and call its EndInvoke to get back the result.

private void CalcCallback(IAsyncResult ar)

{

    // Following line is used when this callback method can not acquire a handle of the delegate, mDeleg, for

    // example, when it is in another class. The AsyncDelegate property was generated by the compiler together

    // with the BeginInvoke and EndInvoke methods.

    // CalcDelegate mDeleg = (CalcDelegate)(ar.AsyncDelegate);

 

    string strResult = null;

 

    int iResult = mDeleg.EndInvoke(ref strResult, ar);

 

    tbResult.Text = Convert.ToString(iResult); // the third textbox

    tbMsg.Text = strResult; // the message textbox

    int i = (int)ar.AsyncState;

    MessageBox.Show("The ar.AsyncState is " + i); // will show 123456789

}

The IAsyncResult object that is passed to the callback method does not contain the result of the time-consuming method. Instead it contains the identity of the calling method and a handle to the delegate. This asynchronous process is like a dry cleaning store with a procedure a bit different from common ones. You call in first, leave a job to be done, and your address – the callback method. When the job is done, the store contacts you through the address you left, and provides a job number. You go back to the store with the job number to pick up the job done. The IAsyncResult object contains this job number. You have to call the time-consuming method again with this job number to get back the real result.

To do the invoke in one thread:

CalcDelegate deleg = new CalcDelegate(Calc);

//AsyncCallback callback = new AsyncCallback(CalcCallback);

int input1 = Convert.ToInt16(tbInput1.Text);

int input2 = Convert.ToInt16(tbInput2.Text);

string strResult = null; // dummy parameter

IAsyncResult ar = deleg.BeginInvoke(input1, input2, ref strResult, null, new object());

//deleg.BeginInvoke(input1, input2, ref strResult, callback, new object());

ar.AsyncWaitHandle.WaitOne();

 

strResult = null;

int iResult = deleg.EndInvoke(ref strResult, ar);

tbResult.Text = Convert.ToString(iResult);

tbMsg.Text = strResult;

Note the following difference:

1.       No need to pass a callback to BeginInvoke;

2.       UI will freeze while waiting;

3.       The same delegate handle is used to call both BeginInvoke and EndInvoke.

1.1.        Event

The event architecture offers a mechanism for an event generator to notify an event consumer that something happened. The event generator does not need to know anything about the consumer. Instead it publishes a method definition in the form of a delegate, plus an event handle. When the event happens, the generator will call the unknown event handling method of the consumer through the event handle. A third party which knows both the generator and the consumer is responsible to connect the consumer’s event handling method with the published event handle of the generator.

Example:

// The event and the event args are defined by the event generator.

public class MyEventArgs : EventArgs

{

    private string mstrName;

 

    public MyEventArgs(string strName)

    {

        mstrName = strName;

    }

 

    public string Name

    {

        get { return mstrName; }

        set { mstrName = value; }

    }

}

 

public delegate void MyEvent(object sender, MyEventArgs e);

 

public class EventGenerator

{

    public event MyEvent MyEventExposed;

 

    public void GenerateEvent()

    {

        if (MyEventExposed != null)

        {

            MyEventArgs args = new MyEventArgs("MyEvent");

            MyEventExposed(this, args);

        }

        else

            Console.WriteLine("Event not connected to event consumer!");

    }

}

 

public class EventConsumer

{

    public void HandleEvent(object sender, MyEventArgs e)

    {

        Console.WriteLine("Event name: \"" + e.Name + "\"");

    }

}

 

public class EventConnector

{

    public static void Main (string[] args)

    { 

        EventConsumer consumer = new EventConsumer();

EventGenerator generator = new EventGenerator();

        generator.MyEventExposed += new MyEvent(consumer.HandleEvent);

        generator.GenerateEvent();

        Console.ReadLine();

    }

}

1.2.        Convert Between Objects & byte Array with BinaryFormatter

In the following example, we create a object of a simple class Car, convert it into a byte array, and convert it back.

using System;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

 

namespace For_Testing_Simple_Code

{

    [Serializable]

    class Car

    {

        private string mstrMake;

        private string mstrModel;

        private int miYear;

        private string mstrReg;

 

        public Car(string strMake, string strModel, int iYear, string strReg)

        {

            mstrMake = strMake;

            mstrModel = strModel;

            miYear = iYear;

            mstrReg = strReg;

        }

 

        override public string ToString()

        {

            return mstrMake + ", " + mstrModel + ", " + miYear + ", " + mstrReg;

        }

    }

 

 

    class Class1

    {

        [STAThread]

        static void Main(string[] args)

        {

            Car car1 = new Car("Toyota", "Camry", 2001, "ABC123");

 

            MemoryStream memStream1 = new MemoryStream();

            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(memStream1, car1);

            byte [] bytes = memStream1.GetBuffer();

            memStream1.Close();

 

            MemoryStream memStream2 = new MemoryStream(bytes);

            Car car2 = (Car)formatter.Deserialize(memStream2);

            Console.WriteLine(car2.ToString());

            Console.ReadLine();

        }

    }

}

The output is:

Toyota, Camry, 2001, ABC123

1.3.        Encrypting and Decrypting with DES Crypto Service Provider

using System;

using System.IO;

using System.Security.Cryptography;

 

namespace For_Simple_Test

{

    class Class1

    {

        [STAThread]

        static void Main(string[] args)

        {

            // Both desKey and desIV could be any 8-byte array.

            byte [] desKey = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};

            byte [] desIV = {0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78};

       

 

            //**** Encryption Process ****

 

            FileStream fsOriginal = new FileStream("Original.txt", FileMode.Open, FileAccess.Read);

            FileStream fsEncrypted = new FileStream("Encrypted.txt", FileMode.OpenOrCreate, FileAccess.Write);

            byte[] buffer = new byte[5000];

       

            DESCryptoServiceProvider des = new DESCryptoServiceProvider(); 

 

            CryptoStream encryptStream = new CryptoStream(

               fsEncrypted,

               des.CreateEncryptor(desKey, desIV),

               CryptoStreamMode.Write);

                

            //Read from the input file, then encrypt and write to the output file.

            int iLenth = fsOriginal.Read(buffer, 0, 5000);

            encryptStream.Write(buffer, 0, iLenth);

            encryptStream.Close(); 

            fsOriginal.Close();

            fsEncrypted.Close();

       

 

            //**** Decryption Process ****

 

            fsEncrypted = new FileStream("Encrypted.txt", FileMode.Open, FileAccess.Read);

            FileStream fsDecrypted = new FileStream("Decrypted.txt", FileMode.OpenOrCreate, FileAccess.Write);

 

            CryptoStream decryptStream = new CryptoStream(

               fsDecrypted,

               des.CreateDecryptor(desKey, desIV),

               CryptoStreamMode.Write);

 

            iLenth = fsEncrypted.Read(buffer, 0, 5000);

            decryptStream.Write(buffer, 0, iLenth);

            decryptStream.Close(); 

            fsEncrypted.Close();

            fsDecrypted.Close();

        }

    }

}