Friday 11 July 2014

Text parsing within a simple app

By profession, I am a qualified lecturer in Chemistry. I've been doing it for years and one aspect which is key to understanding is the ability to understand concentration and the mole. To most people, a mole is a cute little creature that digs people's lawns up and that's about it. To a chemist, it's possibly one of the fundemental parts and it causes more problems than enough.

After teaching one particular group, I decided to sit down a write a chemical formula calculator. I know there are lots of these on the market but there was a problem. Some charge, some don't and some aren't very good. Key to mine would be a simple to use interface.

The user would enter the formula and then the number of moles required. The program would do what it needed to and report back it's answers. Easy enough.

As with any piece of software, the majority of the effort is in the design and understanding of the problem

The problem


Take the formula CuSO4.5H2O - this is a compound called copper (II) sulfate pentahydrate. If you did science at school, you'll possibly have grown crystals with it as it has a very attractive deep blue colour.

A chemist would look at this and work out how much it weights like this:

  1. There is a dot, this means that there are two parts to the formula
  2. On the left, there is a Cu, an S and an O followed by a 4. As there are no numbers before the Cu and the S this means there is only one of these.
  3. On the right there is an H followed by a 2 and then an O with nothing after it.
  4. Before the H though, there is a 5. This means that there is 5 lots of whatever follows, so there is now 10 lots of H and 5 lots of O
  5. Overall then, there is 1 Cu, 1 S, 9 O and 10 H.
  6. Find the mass of each element, multiply it by the number of it and add the lot together

This sort of calculation can be performed in a matter of moments by any chemistry student - in fact, a non-chemist can do it as long as they know what each element weights, it's just simple maths

A program though has to handle things differently...

For a start, elements always start with a capital letter, but don't have to just have one letter. Brackets also have to be understood, and what happens if there is a dot at the end?

With a bit of logic and quite a bit of paper, the solution wasn't that hard...

Knowing the problem, the solution can be found. First off, let's define some data for the elements

I'm doing this by first creating a string array for the elements and then a double array for the masses. I also have a List of type element which will be used to store the elements in (makes life simpler when doing comparisons - I did not use the list to store in initially for reasons which should become clear later)

public class element
{
  public string el;
  public double num;           
  public element(string e, double n)
  {
     this.el = e;
     this.num = n;
  }
}

List <element> elem = new List();
       
string[] elements = new string[] {"H","He",
  "Li","Be","B","C","N","O","F","Ne",
  "Na","Mg","Al","Si","P","S","Cl","Ar",
  "K","Ca","Sc","Ti","V","Cr","Mn","Fe","Co","Ni","Cu",
  "Zn","Ga","Ge","As","Se","Br","Kr",
  "Rb","Sr","Y","Zr","Nb","Mo","Tc","Ru","Rh","Pd","Ag",
  "Cd","In","Sn","Sb","Te","I","Xe",
  "Cs","Ba","La",
  "Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er",
  "Tm","Yb","Lu",
  "Hf","Ta","W","Re","Os","Ir","Pt","Au","Hg","Tl","Pb",
  "Bi","Po","At","Rn",
  "Fr","Ra","Ac",
  "Th","Pa","U","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",
  "Md","No","Lr",
  "Rf","Db","Sg","Bh","Hs","Mt", "Ds", "Rg"};
       
double [] atmass = new double[111] {1.0079,4.0026,
  6.941,9.01218,10.8,12.011,14.0067,15.9994,18.9984,20.179,
  22.9898,24.305,26.9815,28.0855,30.9738,32.06,35.453,39.948,
  39.0983,40.08,44.9559,47.88,50.9415,51.996,54.9380,55.847,
  58.9332,58.69,63.546,65.38,69.72,72.59,74.9216,78.96,79.904,83.8,
  85.4679,87.62,88.9059,91.22,92.9064,95.94,98,101.07,102.9055,
  106.42,107.868,112.41,114.82,118.69,121.75,127.6,126.9045,131.29,
  132.9054,137.33,138.9055,
  140.12,140.9077,144.24,145,150.36,151.96,157.25,158.9254,162.5,
  164.9304,167.26,168.9342,173.04,174.967,
  178.49,180.9479,183.85,186.207,190.2,192.22,195.08,196.9665,
  200.59,204.383,207.2,208.9804,209,210,222,
  223,226.0254,227.0278,
  232.0381,231.0359,238.0289,237.0482,244,243,247,247,251,252,
  257,258,259,260,
  261,262,263,264,265,266,267,268 };

 string [] numbers = new string[10] {"0","1","2","3",
                                     "4","5","6","7",
                                     "8","9"};


That's the elements and atomic weights all in. I've also added in another array which just stores numbers. These are to be used later on as well.

Test 1 - the text box

The first test that needs to be carried out is not on any sort of formula, but on the text entry box itself. There are a few tests that can be done here. The two simplest are to check if there is something actually in there and then if what's in there is valid (in other words, has the user entered an invalid element). These are two easy tasks. Other checks are also performed (for example missing braces)

void calculate(object sender, EventArgs e)
{
   if (formula.Text.Length == 0)
   {
      MessageBox.Show("Error : You haven't entered a formula",
                      "Formula error",
                      MessageBoxButtons.OK,MessageBoxIcon.Error);
      return;
   }


The simplest of errors - if the calculate button has been pressed, nothing can happen if there is nothing to work with

   if ((formula.Text.Contains("(") && !formula.Text.Contains(")"))||
        (formula.Text.Contains(")") && !formula.Text.Contains("(")))
   {
      MessageBox.Show("Error : You have a missing brace within your
                       formula. Please recheck",
                      "Bracket error", MessageBoxButtons.OK,
                      MessageBoxIcon.Error);
      return;
   }


A quick check to ensure that the braces match up. This only checks to make sure there is a ( and a ), but not the number of them. It would be quite trivial to add in a number of braces check.

   if (formula.Text.Length == 1)
   {
      bool test = true;   
      foreach (string element in elements)
      {
         if (formula.Text.Contains(element))
         {
            test = false;
             continue;
         }
      }
      if (test == true)
      {
         MessageBox.Show("Error : Your formula contains an
                         unknown element",
                         "Unknown element", MessageBoxButtons.OK,
                         MessageBoxIcon.Error);
         return;
      }
    }


Let's make sure that the elements entered in the formula actually exist shall we...

    if (formula.Text.Contains("."))
    {
       int pos = formula.Text.IndexOf(".");
       if (pos == formula.Text.Length)
       {
          MessageBox.Show("Error : You have a period followed by
                          nothing",
                          "Period error", MessageBoxButtons.OK,
                          MessageBoxIcon.Error);
          return;
       }
     }


Okay, we have a dot in the formula. Let's do a quick sanity check - is there anything after it?

     string dupeform = formula.Text;

     if (dupeform.Contains("("))
        dupeform = dupeform.Remove(dupeform.IndexOf("("), 1);
     if (dupeform.Contains(")"))
        dupeform = dupeform.Remove(dupeform.IndexOf(")"), 1);
     if (dupeform.Contains("."))
        dupeform = dupeform.Remove(dupeform.IndexOf("."), 1);
     for (int a = 0; a < dupeform.Length; ++a)
     {
        foreach (string n in numbers)
        {
          if (dupeform.Contains(n))
             dupeform = dupeform.Remove(dupeform.IndexOf(n), 1);
        }
     }


This part may strike as being a bit odd. I've made a duplicate copy of the validated text and have then removed all of the numbers, braces and periods - it's just a sanity check. It's now clear why I've created a string containing numbers
   
       dupeform = formula.Text + "#";
   search(dupeform, false);
   results();
}


The duplicate is then passed to the search routine and the results method called

The Search routine

Now that the code has through the formula, it's time to do the leg work and find out what the mass of the compound is.

The search is split into two parts - the first divides up the formula, the second identifies the element.

The search is also a recursive search - it makes more sense to parse the first part of a formula, then parse the rest instead of doubling up the code in order to do the same thing. I'm not a fan of recursive functions as the logic is not always that clear with them, but in this case, it's a good idea to use it

void search(string formula, bool dot)
{
   int mult1 = 1, mult2 = 1, s = 0, p = 0, bs = 0, be = 0, bn = 0;
   bool hasdot = formula.Contains(".") ? true:false;
   bool hasbrace = formula.Contains("(") ? true : false;
   int point = hasdot == true ? formula.IndexOf(".") + 1 : 0;
       
   if (hasbrace == true)
   {
      int k = 0;
      for (k = 0; k < formula.Length; ++k)
      {
         if (formula[k] == '(')
            bs = k;
         if (formula[k] == ')')
            be = k;
      }
      k = formula.IndexOf(")") + 1;
      if (formula[k] >= '0' && formula[k] <= '9')
      {
          int c = 0;
          while (formula[k + c] >= '0' && formula[k + c] <= '9')
             c++;
          bn = Int32.Parse(formula.Substring(k, c));
      }
      else
          bn = 1;
    }


Is there a number outside of a bracket? If there is, what is it? The start and end points of the braces are also found and stored here
           
    if (hasdot == true && dot == true)
    {
       if (formula[point] >= '0' && formula[point] <= '9')
       {
          int c = 0;
          while(formula[point + c] >= '0' &&
                formula[point + c] <= '9')
             c++;
          mult1 = Int32.Parse(formula.Substring(point, c));
          s = point + 1;
       }
     }
     else
     {
        if (formula[0] >= '0' && formula[0] <= '9')
        {
           int c = 0;
           while(formula[c] >= '0' && formula[c] <= '9')
              c++;               
           mult1 = Convert.ToInt32(formula.Substring(0, c));
           s = 1;
         }
     }


Here the number at the start is being found. If nothing is there, mult1 remains at 1 - this is used to multiply the mass found by.

The hard part now starts - first is that it's vital to remember that an element can have more than one character, so let's set up some simple variables to help

string twoelem = "   "; // three spaces
double newmass = 0;
           
if (hasdot == true && dot == false)
   p = formula.IndexOf(".");
else
   p = formula.Length + 1;


The if clause really just sets the length of the string to be searched. The searching is all done with one large loop...

int loop = s;
{
   while (loop < p)
   {
      if (loop + 1 > p || formula[loop] == '#')
         break;

      if (loop == bs)
      {
         while (loop < be)
         {
            if (formula[loop + 1] >= 'a')
            {
               twoelem = formula.Substring(loop, 2);
               loop += 2;
            }
            else
            {
               twoelem = formula.Substring(loop, 1);
               loop++;
            }

            if (formula[loop] >= '0' && formula[loop] <= '9')
            {
               int c = 0;
               while (formula[loop + c] >= '0' &&
                      formula[loop + c] <= '9')
                  c++;
               if (twoelem == "")
               {
                  if (formula[loop + c + 1] < 'a')
                     twoelem = formula.Substring(loop + c, 1);
                  else
                     twoelem = formula.Substring(loop + c, 2);
               }
               mult2 = Int32.Parse(formula.Substring(loop, c));
               loop += c;
            }
            newmass = atommass(twoelem) * mult2 * bn;
            elem.Add(new element(twoelem, newmass));
            newmass = 0;
            mult2 = 1;
         }
      }
     
      if (formula[loop + 1] >= 'a')
      {     
         twoelem = formula.Substring(loop, 2);
         loop += 2;
      }
      else
      {
         twoelem = formula.Substring(loop, 1);
         loop++;
      }

      if (formula[loop] >= '0' && formula[loop] <= '9')
      {
         int c = 0;
         while (formula[loop + c] >= '0' &&
                formula[loop + c] <= '9')
            c++;
           
         if (twoelem == "")
         {
            if (formula[loop + c + 1] < 'a')
               twoelem = formula.Substring(loop + c, 1);
            else
               twoelem = formula.Substring(loop + c, 2);
         }
         mult2 = Int32.Parse(formula.Substring(loop, c));
         loop += c;
      }
      newmass = atommass(twoelem) * mult2 * mult1;
      elem.Add(new element(twoelem, newmass));
      newmass = 0;
      mult2 = 1;
   }
}


And finally, let's do the recursion...

if (dot == false && hasdot == true)
   search(formula, true);

Using Resources within your Winform application

Typically, there are two types of resources available to any piece of software - those held within the code and those outside of the code. Both have their advantages and both their disadvantages.

If a resource (say an image) is compiled into the final assembly, the final assembly can be huge, but the nice thing is that everything is inside the assembly, so no danger pictures or sounds going missing causing the final application not work

If a resource is outside of the application, the benefit is a smaller assembly size, but everything needs to be loaded in which slows things down and has an added draw back of the application failing if a resource is missing.

A big advantage though of the resource being outside of the compiled binary is that if the development or proof of concept is done in VB.NET, the final version can be written in C# and use the same resources used.

Resources are handled using the System.Resources class. The following example loads a file which is a resource. In this case, it loads a picture

var executingAssembly = Assembly.GetExecutingAssembly();
try
{
   var resourceStream =
      executingAssembly.GetManifestResourceStream(xml.picname);
   pictureBox1.Image = new Bitmap(resourceStream);
}
catch (System.ArgumentException)
{
   MessageBox.Show("Unable to find element picture",
                   "Picture not found", MessageBoxButtons.OK);
}


xml.picname is read in from another method and just contains the name of an image held in the resources directory.

Number on textbox with Winforms

The WinForm textbox is a useful widget. You can enter just about anything into it. It is possible to set a flag and make it into a password entry box where all input is substituted for something else (such as an asterix). However, there is a limitation - you can't use it for numbers only. Okay, you could use a number up/down widget, but then there is a problem of having the up/down arrows.

There are two ways of using a textbox for numbers only. The first is to read the contents and use RegEx, but this is a bit of a pain and you have to understand RegEx. A much simpler method is to extend the Textbox class

class NumberBox : TextBox
{
   public NumberBox()
   {
      this.CausesValidation = true;
      this.Validating += new CancelEventHandler(TextBox_Validation);
   }

   private void TextBox_Validation(object sender, CancelEventArgs e)
   {
      try
      {
         int value = System.Int32.Parse(this.Text);
      }
      catch (System.Exception)
      {
          e.Cancel = true;
      }
   }
}


Pretty much, that's all there is to it - if you enter a number, all is well. Enter text and it won't let you

Passing information around - using an Interface

One aspect of C# which has room for improvement is that of inheritance. In C++, a class can inherit as many other classes as it needs to. This multiple inheritance is sadly lacking in C# - the most that can be inherited is a single class.

The way C# gets around this problem is by using interfaces. Interfaces are special - you can define methods in there, but that's about it. Think of it as a C header file - it contains the prototypes for functions but not the variables.

interface Isettings
{
    void loadsettings(bool group);
    void resetsettings();
    void saveSettings(bool group);
}

public class foo : Form, Isettings
{
   public foo()
   {
   }

   public void loadsettings(bool group)
   { 
  

Here, the class foo inherits the System.Windows.Form class as well as the Isettings interface (it happens to define the method properly here - but the interface can be used anywhere and as many interfaces can be inherited as well.

Another method is to simply define a class and then create a new instance of it in whatever other class needs it - this can be performed in one of two ways

public class common
{
   public int intro, meth, result, discuss, addition;
   public decimal final;
   public string date;
   public bool groups;
}

public class MainForm
{
   public MainForm()
   {
      this.InitializeComponent();
   }
   common data;
   // more code
}


I've not created a new instance of common here, just created a local pointer to it. If anything is used from this pointer which has not been given a value (for example if I did the following and result has not been defined int fish = result * 10;), then the final result may not be what is expected.

As it is a pointer, you're working on the stack and odd things happen when you're on the stack... If you change something, it's stored and everyone gets the change

The final method is a to create a new instance of a class

var settings = new XmlReaderSettings();

A completely new instance of the class XmlReaderSettings has been created here - the structure, methods and anything defined in XmlReaderSettings has been replicated in settings. If settings is changed, it does not change XmlReaderSettings

It is possible to pass either the pointer to the class or the instance of the class to another method in the same way as you would pass any other type

var settings = new XmlReaderSettings();
m.Text = some_function(settings);
//
private string some_function(XmlReaderSettings settings)
{
   //
}

Using XML Part 2

XML does not have to be as simple as the previous example. XML can be serialized. Serialization allows the user to read or write data to an object which can then be later manipulated. A good example of this would be a school mark book.

A class contains 10 students. Every day, they have 5 lessons which they may or may not have homework in. If there is a homework, the mark has to be stored. This is a perfect example for serialized data. To start with, the XML file would look like this:

<student>
   <name></name>
   <date></date>
   <absent></absent>
   <lesson1></lesson1>
   <lesson2></lesson2>
   <lesson3></lesson3>
   <lesson4></lesson4>
   <lesson5></lesson5>
</student>


The code has to be tagged in the source files as [Serializable] in order for the code to work.

To deserialize code from XML, it is as simple as this (FormList is a class which is used to hold the serialized XML)

FormList f;
f = null;

var s = new XmlSerializer(typeof(FormList));
var r = new StreamReader(place + "designer-test.xml");
f = (FormList)s.Deserialize(r);
r.Close();


The FormList looks like this

[Serializable]
[XmlRoot("Forms")]
public class FormList
{
    private List forms;

    public FormList()
    {
       forms = new List();
    }
    [XmlElement("Form")]
    public FormData[] Forms
    {
       get { return this.forms.ToArray(); }
       set { this.forms = new List(value); }
    }
}


[Serializable]
public class FormData
{
   public List elements;
   public List question;
       
   public FormData()
   {
      elements = new List();
      question = new List();
   }

   public string WinName { get; set; }
   public int WinWidth { get; set; }
   public int WinHeight { get; set; }
   public string WinTitle { get; set; }
   public int BackLink { get; set; }
   public int ForwardLink { get; set; }
   public int PageNumber { get; set; }
   public int ElementNos { get; set; }

   [XmlElement("Element")]
   public Element[] Elements
   {
      get { return this.elements.ToArray(); }
      set { this.elements = new List(value); }
   }

   [XmlElement("Question")]
   public Qs[] questions
   {
      get { return this.question.ToArray(); }
      set { this.question = new List(value); }
   }
}


Forms uses FormData which is what that does the leg work. Note it is the class that is Serializable not the methods.

Using XML - Part 1

The best way to understand XML is to look at two different XML files. The first one is a simple one

<?xml version="1.0"?>
<configure>
  <ljmu>
     <Introduction>20</Introduction>
     <Method>10</Method>
     <Results>20</Results>>
     <Discussion>40</Discussion>
     <Additional>10</Additional>
     <date>28-11-2007</date>
  </ljmu>
  <access>
     <Introduction>20</Introduction>
     <Method>10</Method>
     <Results>20</Results>
     <Discussion>40</Discussion>
     <Additional>10</Additional>
     <date>28-11-2007</date>   
  </access>
</configure>


It is simple enough to parse through a structure like this. ident is the node to be searched for and attr is the node. XmlReader.Create(conpath, settings, null) creates a reader stream.

public int dotheread(string ident, string attr)
{
   var settings = new XmlReaderSettings();
   var reader = XmlReader.Create(conpath, settings, null);
           
   int retval = -1;
     
   reader.MoveToAttribute(attr);
           
   while (reader.Read())
   {
      if (reader.NodeType == XmlNodeType.Element)
      {
         if (reader.Name == ident)
         {   
                reader.Read();
            retval = Convert.ToInt32(reader.Value);
            break;
         }
      }
    }               
                       
    reader.Close();
           
    if (retval == -1)
    {
       DialogResult result;
       result = MessageBox.Show(this, "Unable to find the element",
                                "D'oh!", MessageBoxButtons.OK);
    }
           
    return retval;
}


Writing XML like this is also very simple

var xmlWriter =  new XmlTextWriter(conpath,null);
           
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("configure");
   xmlWriter.WriteStartElement("ljmu");
      xmlWriter.WriteElementString("Introduction","20");
      xmlWriter.WriteElementString("Method","10");
      xmlWriter.WriteElementString("Results","20");
      xmlWriter.WriteElementString("Discussion","40");
      xmlWriter.WriteElementString("Additional","10");
      xmlWriter.WriteElementString("Date", dateToday);
   xmlWriter.WriteEndElement();
   xmlWriter.WriteStartElement("access");
      xmlWriter.WriteElementString("Introduction","20");
      xmlWriter.WriteElementString("Method","10");
      xmlWriter.WriteElementString("Results","20");
      xmlWriter.WriteElementString("Discussion","40");
      xmlWriter.WriteElementString("Additional","10");
      xmlWriter.WriteElementString("Date", dateToday);
   xmlWriter.WriteEndElement();
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
           
xmlWriter.Close(); 
  

This is a very simple example, but it can be much more entertaining...

Finding your IP address

Different operating systems have different methods of finding the IP address. Unfortunately, none of them are platform neutral (in other words, what someone under Linux would do is not what someone using a Windows box would do). If I didn't care about the underlying operating system I could simply read in the results of either /sbin/ifconfig or ipconfig /all (both return all of the network addresses on a machine, all that has to be done is search the text for something that isn't either 127.0.0.1 or 0.0.0.0.) - fine if I know what the target machine is.

But what if I don't? How can I find my IP address in a way that will work on all machines, irrespective of if the framework is Mono based or Microsoft based?

Thankfully, it's as easy as this...

using System;
using System.Net;

namespace dns
{
    public class dns
    {
        public static int Main (string [] args)
        {
       
          String strHostName = "";
          if (args.Length == 0)
          {
              // Getting IP address of local machine...
              // First get the host name of local machine.
              strHostName = Dns.GetHostName ();
              Console.WriteLine ("Local Machine's Host Name: " + 
                                 strHostName);
          }
          else
          {
              strHostName = args[0];
          }
         
          // Then using host name, get the IP address list..
          // IPHostEntry ipEntry = Dns.GetHostByName (strHostName);
          IPHostEntry ipEntry = Dns.GetHostEntry(strHostName);
          IPAddress [] addr = ipEntry.AddressList;
         
          int ips = 0;
          foreach (IPAddress ipaddr in ipEntry.AddressList)
          {
              Console.WriteLine("IP #" + ++ips + ": " +
                                ipaddr.ToString());
          }
          Console.ReadKey();
          return 0;
        }   
    }
}

Using C# with Gmail - Part 2, SMTP

POP3 isn't actually natively supported in .NET, but as has been shown, it's pretty easy to do. Next up is SMTP. This is much simpler as .NET has this built into the System.Net.Mail class.

Sending email


Really, the only thing hard about sending email is getting things in the correct order... Here I've the send email method as an event - it would be very simple to write a wrapper around it

This event is linked to a very simple winform which you can see if you download the source code

void SendClick(object sender, EventArgs e)
{
   MailMessage mail = new MailMessage();
   mail.To.Add("second_user@gmail.com");


It's always a good idea to say who you're email is from. Nice thing here is that if you have a wrapper around the SMTP code, you can email different people. For my example here, I just wanted to email another user account to ensure it works. When you do this sort of thing, having a second account always helps...

   mail.From =
      new System.Net.Mail.MailAddress("your_username@gmail.com");
   if (subject.Text.Length != 0)
      mail.Subject = subject.Text;
   else
      mail.Subject = "I forgot to fill it in";


While there is nothing that says you must have a subject, it's a good idea to. Quite a lot of email spam blockers user a NULL in the message as a fairly high scorer on the factors when it decides if email is spam or not

   mail.Body = message.Text;

If you had any attachments can be added on here. Effectively, this is the header of the email done. Now all that needs to be done is to send it
   
   System.Net.Mail.SmtpClient smtp =
       new System.Net.Mail.SmtpClient("smtp.gmail.com");
   smtp.EnableSsl = true;
   smtp.Credentials =
       new System.Net.NetworkCredential("your_username",
                                        "your_password");
   try
   {
      smtp.Send(mail);
   }
   catch (System.Net.Mail.SmtpException ex)
   {
      MessageBox.Show(ex.Message.ToString(),
                      "Unable to connect to the remote server",
                      MessageBoxButtons.OK);
      return;
   }
}

I did say it wasn't difficult!

Using C# with Gmail - Part 1, POP3

While this part is for gmail, it can also be used for other email providers. I'm also assuming that you have a gmail account. If you haven't, you can still use the code, but you'll need to alter some parts.

Introduction


There are two parts to any form of email application - the sending and the receiving. Neither of these are hard to code, but but they can get a bit messy. POP3 is not natively supported in .NET, so it will need coding up. Thankfully, it's not difficult.

Connecting to POP3


The POP3 system allows software to connect to a remote server and using a predefined set of instructions, access emails. Gmail requires an SSL connection. SSL is supported in System.Net.Security. The actual connection is quite simple.

The strings username and password are exactly what they say they are - your username and password

SslStream nstream;
       
public SslStream Connect(string username, string password)
{
   string message;
   string response;
   TcpClient tcpClient = new TcpClient();
   tcpClient.Connect("pop.gmail.com", 995);

995 is the port needed to connect to the gmail POP3 server. Notice at this point, nothing has been done with the SSL connection - all that has happened is that a connection has been made by the software to a port.

   nstream = new SslStream(tcpClient.GetStream());
   nstream.AuthenticateAsClient("pop.gmail.com");
   response = Response(nstream);

OK, we've now connected to the server using SSL (tcpClient.GetStream() is used to obtain the underlying network stream in use) and it's time to listen to the server

   if (response.Substring(0, 3) != "+OK")
   {
      throw new Pop3Exception(response);
   }

   message = "USER " + username + "\r\n";
   Write(message, nstream);
   response = Response(nstream);
   if (response.Substring(0, 3) != "+OK")
   {
      throw new Pop3Exception(response);
   }

   message = "PASS " + password + "\r\n";
   Write(message, nstream);
   response = Response(nstream);
   if (response.Substring(0, 3) != "+OK")
   {
      throw new Pop3Exception(response);
   }
   return nstream;
}

At this point the software has connected to the server. The above method has two helper methods associated with it, Response and Write. These are the main communication methods for the server, so let's detail them now.

private void Write(string message, SslStream nstream)
{
   System.Text.ASCIIEncoding en = new System.Text.ASCIIEncoding() ;
   byte[] WriteBuffer = new byte[1024] ;
   WriteBuffer = en.GetBytes(message) ;
    nstream.Write(WriteBuffer, 0, WriteBuffer.Length);
}
       
private string Response(SslStream stream)
{
   System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
   byte[] serverbuff = new Byte[1024];
   int count = 0;
   while (true)
   {
      byte[] buff = new Byte[2];
      int bytes = stream.Read(buff, 0, 1 );
      if (bytes == 1)
      {
          serverbuff[count] = buff[0];
          count++;

          if (buff[0] == '\n')
          {
             break;
          }
       }
       else
       {
          break;
       };
    };

    string retval = enc.GetString(serverbuff, 0, count );
    return retval;
}

Disconnecting from the server is just a case of sending the disconnect message, waiting for the reply and well, that's about it

public void Disconnect(SslStream netstream)
{
   string message;
   string response;
   message = "QUIT\r\n";
   Write(message, netstream);
   response = Response(netstream);
   if (response.Substring(0, 3) != "+OK")
   {
      throw new Pop3Exception(response);
    }
}