C# Tutorial
Silan Liu
1. Basics and Individual Topics
1.1. Generating Schema, Class and DataSet from XML Using xsd.exe
1.2. Asynchronous Programming with Delegates
1.4. Convert Between Objects & byte Array with BinaryFormatter
1.5. Encrypting and Decrypting with DES Crypto Service Provider
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
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.
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
{
EventConsumer
consumer = new EventConsumer();
EventGenerator generator = new EventGenerator();
generator.MyEventExposed
+= new MyEvent(consumer.HandleEvent);
generator.GenerateEvent();
Console.ReadLine();
}
}
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
{
Car
car1 = new Car("
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
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();
}
}
}