.NET 2.0 and C# 2 New Features

Content:

  1. Version numbers
  2. Release
  3. FTP support
  4. Ping support
  5. Web server support
  6. GZip support
  7. Console colors
  8. Partial classes
  9. Database connections
  10. Generics
  11. Other new features

Version numbers:

.NET framework, .NET runtime, C# language/compiler and Visual Studio IDE each have their own version numbering scheme. For this release they are at 2.0, 2.0, 2 and 2005 respectively.

.NET framework .NET runtime C# language/compiler Visual Studio IDE Release date
1.0 1.0 1.0 2002 February 13th 2002
1.1 1.1 1.2 2003 April 24th 2003
2.0 2.0 2.0 2005 November 7th 2005

Release:

.NET 2.0 and VS 2005 was released November 7th 2005.

FTP support:

A really frustrating gap in .NET 1.x is that WebRequest only supports HTTP and not FTP. That is fixed in .NET 2.0:

using System;
using System.IO;
using System.Net;

class Ftp2
{
    public static void Main(string[] args)
    {
        FtpWebRequest req = (FtpWebRequest)WebRequest.Create("ftp://192.168.1.10/utilities/utilities.zip");
        FtpWebResponse resp = (FtpWebResponse)req.GetResponse();
        Stream f1 = resp.GetResponseStream();
        Stream f2 = new FileStream("utilities.zip", FileMode.CreateNew, FileAccess.Write);
        byte[] b = new byte[1000];
        int n;
        while((n = f1.Read(b, 0, b.Length)) > 0)
        {
            f2.Write(b, 0, n);
        }
        f2.Close();
        f1.Close();
        resp.Close();
    }
}

This is a useful feature.

I expect all developers supporting more than just HTTP to like this feature.

Ping support:

.NET 2.0 has also gotten a Ping class:

using System;
using System.Net.NetworkInformation;
using System.Text;

public class Ping2
{
    public static void Main(string[] args)
    {
        Ping p = new Ping();
        PingReply pr = p.Send("www.tv2.dk");
        if(pr.Status == IPStatus.Success)
        {
            Console.WriteLine("TV2 oppe (tid = " + pr.RoundtripTime + " ms)");
        }
        else
        {
            Console.WriteLine("TV2 nede");
        }
   }
}

Nice feature for those that need it.

And I sure those that need it will like it.

Web server support:

.NET 2.0 has also gotten a framework for writing a simple web server:

using System;
using System.IO;
using System.Net;

// call with:
//   http://localhost/test/show
//   http://localhost/test/exit
public class WebServer2
{
    public static void Main(string[] args)
    {
        HttpListener srv = new HttpListener();
        srv.Prefixes.Add("http://localhost/test/");
        srv.Start();
        while(true)
        {
            HttpListenerContext ctx = srv.GetContext();
            HttpListenerRequest req = ctx.Request;
            HttpListenerResponse resp = ctx.Response;
            StreamWriter sw = new StreamWriter(resp.OutputStream);
            if(req.Url.AbsolutePath == "/test/exit")
            {
                sw.WriteLine("Bye");
                sw.Close();
                break;
            }
            else if(req.Url.AbsolutePath == "/test/show")
            {
                foreach(string hdrnam in req.Headers.AllKeys)
                {
                    string hdrval = req.Headers[hdrnam];
                    sw.WriteLine(hdrnam + " = " + hdrval + "<br>");
                }
            }
            else
            {
                sw.WriteLine("ukendt kommando");
            }
            sw.Close();
        }
        srv.Stop();
        srv.Close();
    }
}

Obviously not many would want to write a competitor to Apache or IIS. But it is very common today with web interfaces to programs without a GUI. So even without interest in competing with Apache and IIS, then it is possible to have a program running in the background to be managed via a browser.

I think this is a useful feature.

I expect some to use it when they have the need. At least if they are aware of it.

GZip support:

.NET 2.0 has builtin support for streams to and from GZip format:

using System;
using System.IO;
using System.IO.Compression;

class GZip2
{
    private static void CopyData(Stream f1, Stream f2)
    {
        byte[] b = new byte[1000];
        int n;
        while((n = f1.Read(b, 0, b.Length)) > 0)
        {
            f2.Write(b, 0, n);
        }
        f2.Close();
        f1.Close();
    }
    public static void Compress(string fnm1, string fnm2)
    {
        CopyData(new FileStream(fnm1, FileMode.Open, FileAccess.Read),
                 new GZipStream(new FileStream(fnm2, FileMode.CreateNew, FileAccess.Write), CompressionMode.Compress));
    }
    public static void Decompress(string fnm1, string fnm2)
    {
        CopyData(new GZipStream(new FileStream(fnm1, FileMode.Open, FileAccess.Read), CompressionMode.Decompress),
                 new FileStream(fnm2, FileMode.CreateNew, FileAccess.Write));
    }
    public static void Main(string[] args)
    {
        Compress("test.txt", "test.txt.gz");
        Decompress("test.txt.gz", "newtest.txt");
    }
}

I think it is a great idea to add compress and decompress streams. But I am skeptical about the choice of GZip format. All *nix people are used to GZip, but Windows people use Zip (Gzip and Zip are based on the same algorithm, but Zip supports multiple files in archive. I think they should have incorporated #ziplib instead.

But the possibility is there now and some will be using it.

UPDATE: .NET 4.5 added Zip support.

Console colors:

And a feature for those coding ealy 90's DOS application style: colors in console:

using System;

public class ConsoleColor2
{
    public static void Main(string[] args)
    {
        ConsoleColor fg = Console.ForegroundColor;
        ConsoleColor bg = Console.BackgroundColor;
        Console.WriteLine("normal");
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.BackgroundColor = ConsoleColor.Red;
        Console.WriteLine("fancy");
        Console.ForegroundColor = fg;
        Console.BackgroundColor = bg;
    }
}

This is more fun than useful.

I expect nobody to use it.

Partial classes:

C# 2 supports that a class is split in multiple parts.

Fragment:

public partial class X
{
    public void m1()
    {
        m2();
    }
}

Rest:

using System;

public partial class X
{
    public void m2()
    {
        Console.WriteLine("Det virker");
    }
    public static void Main(string[] args)
    {
        X x = new X();
        x.m1();
    }
}

Nobody will want to actually write a class in two files. The purpose of this feature is to support wizards. In .NET 1.x is was common with files where some parts was edited via wizard and some parts was edited manually. If one by mistake edited the wizard section manually, then often the wizard could not be used anymore. And even if one did not do that, then the wizard code was often very long making it cumbersome to read the file. That problem is solved now with partial class. The wizard part get in one file and the manual part get in another file. Clear separation of things.

Database connections:

A problem in .NET 1.x compared to ADO (and JDBC) was that it was necesarry to instantiate a database specific class to get a database connection. That made it rather cumbersome to write database independent code.

First workaround in .NET 1.1:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;

public class MultiDb
{
    public static IDbConnection GetConnection(string constr) 
    {
        if(constr.ToUpper().IndexOf("DRIVER=") >= 0)
        {
            return new OdbcConnection(constr);          
        }
        else if(constr.ToUpper().IndexOf("PROVIDER=") >= 0)
        {
            return new OleDbConnection(constr);         
        }
        else if(constr.ToUpper().IndexOf("TRUSTED_CONNECTION=") >= 0 ||
                constr.ToUpper().IndexOf("INTEGRATED SECURITY=") >= 0)
        {
            return new SqlConnection(constr);           
        }
        else
        {
            return null;
        }
    }
}

public class MultiDb11
{
    private static void Test(string constr)
    {
        IDbConnection con = MultiDb.GetConnection(constr);
        con.Open();
        IDbCommand cmd = con.CreateCommand();
        cmd.CommandText = "SELECT * FROM T1";
        IDataReader rdr = cmd.ExecuteReader();
        while(rdr.Read())
        {
            int f1 = (int)rdr[0];
            string f2 = (string)rdr[1];
            Console.WriteLine(f1 + " " + f2);
        }
        con.Close();
    }
    public static void Main(string[] args)
    {
        Test(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Databases\MSAccess\Test.mdb");
        Test("Server=ARNEPC3;Integrated Security=SSPI;Database=Test");
    }
}

A bit cumbersome especially when having to add support for more databases and handle weird connections strings.. Most gave up and just used new SqlConnection or new OleDbConnection in the code. And when started with that then continuing with new SqlCommand and new OleDbCommand was easy.

.NET 2.0 has a much better builtin way of doing that:

using System;
using System.Data;
using System.Data.Common;

public class MultiDb2
{
    private static void Test(string provider, string constr)
    {
        DbProviderFactory dbf = DbProviderFactories.GetFactory(provider);
        IDbConnection con = dbf.CreateConnection();
        con.ConnectionString = constr;
        con.Open();
        IDbCommand cmd = con.CreateCommand();
        cmd.CommandText = "SELECT * FROM T1";
        IDataReader rdr = cmd.ExecuteReader();
        while(rdr.Read())
        {
            int f1 = (int)rdr[0];
            string f2 = (string)rdr[1];
            Console.WriteLine(f1 + " " + f2);
        }
        con.Close();
    }
    public static void Main(string[] args)
    {
        Test("System.Data.OleDb", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Databases\MSAccess\Test.mdb");
        Test("System.Data.SqlClient", "Server=ARNEPC3;Integrated Security=SSPI;Database=Test");
    }
}

If one need to add new database types (assuming they support .NET 2.0), then one need to add them to machine.config or app.config under configuration/system.data/DbProviderFactories..

Example of app.config for FireBird, SQLServer CE and SQLite:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.data>
    <DbProviderFactories>
      <add name="SQLite Data Provider"
           invariant="System.Data.SQLite"
           description=".Net Framework Data Provider for SQLite" 
           type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
      <add name="Microsoft SQL Server Compact Data Provider"
           invariant="System.Data.SqlServerCe"
           description=".NET Framework Data Provider for Microsoft SQL Server Compact"
           type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe" />
      <add name="FirebirdClient Data Provider"
           invariant="FirebirdSql.Data.FirebirdClient"
           description=".Net Framework Data Provider for Firebird"
           type="FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient" />
    </DbProviderFactories>
  </system.data>
</configuration>

This is much better than before. A very useful feature.

I doubt that people will use it (and IDbCommand, IDataReader etc.). They should use it (unless they have very specific needs for database specific featueres), but I still doubt that they will.

Generics:

Generics is a feature known from C++ (since late 1980's) and Java (since version 1.5 in 2004). But note that generics in C# are a bit different than both generics in C++ and generics in Java.

The most common usage of generics is type safe collections.

In .NET 1.x one had to either add a wrapper class or extend the CollectionBase class. Example of the latter:

using System;
using System.Collections;

public class MyData 
{
    private string s;
    public MyData() : this("")
    {
    }
    public MyData(string s)
    {
        this.s = s;
    }
    public override string ToString()
    {
        return s;
    }
    public string S
    {
        get
        {
            return s;
        }
        set
        {
            s = value;
        }
    }
}

public class MyCollection : CollectionBase
{
    public MyData this[int index]
    {
        get
        {
           return (MyData)List[index];
        }
    }
    public int Add(MyData value)
    {
        return List.Add(value);
    }
}

public class TypSafCol11
{
    public static void Main(string[] args)
    {
        MyCollection mycol = new MyCollection();
        mycol.Add(new MyData("A"));
        mycol.Add(new MyData("BB"));
        mycol.Add(new MyData("CCC"));
        for(int i = 0; i < mycol.Count; i++)
        {
            Console.WriteLine(mycol[i]);
        }
    }
}

This is way easier in .NET 2.0 with generics:

using System;
using System.Collections.Generic;

public class MyData 
{
    private string s;
    public MyData() : this("")
    {
    }
    public MyData(string s)
    {
        this.s = s;
    }
    public override string ToString()
    {
        return s;
    }
    public string S
    {
        get
        {
            return s;
        }
        set
        {
            s = value;
        }
    }
}

class TypSafCol2
{
    public static void Main(string[] args)
    {
        List<MyData> mycol = new List<MyData>();
        mycol.Add(new MyData("A"));
        mycol.Add(new MyData("BB"));
        mycol.Add(new MyData("CCC"));
        for(int i = 0; i < mycol.Count; i++)
        {
            Console.WriteLine(mycol[i]);
        }
    }
}

But generics can also be used for other purposes than type safe collections.

Small example:

using System;

public class MyComparer<T> where T : IComparable
{
    public static T Max(T a, T b)
    {
        if(a.CompareTo(b) > 0)
        {
             return a;
        }
        else
        {
             return b;
        }
    }
    public static T Min(T a, T b)
    {
        if(a.CompareTo(b) < 0)
        {
             return a;
        }
        else
        {
             return b;
        }
    }
}

public class Test
{
    public static void Main(string[] args)
    {
        Console.WriteLine(MyComparer<int>.Max(11,2));
        Console.WriteLine(MyComparer<int>.Min(11,2));
        Console.WriteLine(MyComparer<string>.Max("11","2"));
        Console.WriteLine(MyComparer<string>.Min("11","2"));
    }
}

This is a very useful feature.

And I am sure that it will be widely used.

Other new features:

C# 2 has nullable types - classes that wrap structs so they can be null.

Syntax: typename ?

C# 2 has anonymous methods.

Syntax: delegate(argumentlist) { body }

C# 2 has static classes. By putting static keyword on class then the class can only contain static members.

C# 2 allows accessibility to be specified separately for get and set.

Article history:

Version Date Description
1.0 August 5th 2005 Initial version (in Danish) published on Eksperten.dk
1.1 May 25th 2005 Add links
1.2 July 24th 2005 Add release date
1.3 November 22nd 2007 Reflect changes between beta and GA
1.4 December 26th 2008 Minor changes
1.5 February 14th 2010 Minor changes
2.0 August 1st 2016 Translation to English and complete reformatting and publishing here
2.1 October 8th 2016 Add content overview
2.2 March 23rd 2017 Add release dates

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj