Kilka słów na temat C#

Wiktor Zychla

C# ( wymowa si szarp (si jak "sigma") ) to nowy, bardzo dobry język, zaprojektowany przez Microsoft. Na pierwszy rzut oka bardzo przypomina Javę, jednak kilka drobnych niedoróbek Javy zostało wyeliminowanych (na przykład: brak typów wyliczeniowych, brak możliwości przeciążania operatora, brak wskaźników na funkcje). Źródło programu napisanego w C# jest kompilowane do kodu pośredniego (IL), a podczas wykonywania język pośredni kompilowany jest do języka natywnego procesora. Podobnie jak w innych nowoczesnych językach programowania programista nie musi skrupulatnie dbać o zwalnianie zaallokowanej pamięci, ponieważ zajmuje się tym odśmiecacz (garbage collector), który przeszukuje stertę w poszukiwaniu obiektów które przestały być używane.

Aby móc samemu kompilować programy w C#, potrzeba jedynie środowiska uruchomieniowego, zwanego .NET Framework (możliwego do zainstalowania na Windowsach począwszy od W98). Framework można ściągnąć ze strony Microsoftu, zajmuje około 20MB. Istnieje również wersja przeznaczona dla programistów, zwana .NET Framework SDK (około 120MB), która dodatkowo zawiera dokumentację i mnóstwo przykładów. .NET Framework jest DARMOWY!

Po zainstalowaniu .NET Framework lokuje się w specjalnym katalogu w podkatalogu SYSTEM skąd można przywoływać kompilator csc.exe.

Istnieje projekt open-source, który ma na celu przeniesienie C# na platformę Linuxową. Więcej szczegółów na stronie projektu: go-mono.

Oto najprostszy program w C#:


using System;

namespace Project1
{
	public class Ex1
	{
		public static void Main(string[] args)
		{
			Console.WriteLine("The first C# program!");
		}
	}
}

Wystarczy zapisać go jako ex1.cs i skompilować poleceniem csc.exe ex1.cs.

Odpowiedź kompilatora:


D:\EXAMPLE1>C:\WINNT\Microsoft.NET\Framework\v1.0.3705\csc.exe ex1.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

D:\_
zaś wyjściowy plik EXE zajmuje 3072 bajtów.

Model obiektowy C# jest bardzo zbliżony do modelu obiektowego SmallTalka, gdzie wszystkie obiekty dziedziczą z najbardziej ogólnego typu zwanego "object". Typ obiekt sam w sobie implementuje kilka użytecznych metod, które propagują się więc na wszystkie obiekty. Na przykład jedna z nich, "ToString()", służy do konwersji obiektu do typu napisowego. Trywialny przykład:


using System;

namespace Project1
{
	public class A
	{
		public int field;
		public A() {}
	}
	#include 
	public class Ex1
	{
		public static void Main(string[] args)
		{
			A a     = new A();
			a.field = 7;
			Console.WriteLine( a.ToString() );
		}
	}
}
Wynik działania programu:

Project1.A
Dzieje się tak dlatego, że niepokryta metoda ToString() zawsze zwraca nazwę typu obiektu, który tę metodę wywołał. Zmieńmy więc definicję metody:

(...)
	public class A
	{
		public int field;
		public A() {}

		public override string ToString()
		{
			return "Jestem obiektem typu A i przechowuję wartość " + field.ToString();
		}
	}
i zgodnie z oczekiwaniami odpowiedź kompilatora brzmi:

Jestem obiektem typu A i przechowuję wartość 7
Dlaczego nie musimy pokrywać metody ToString() dla typu int gdy wołamy field.ToString()? Proste, dla wielu typów ta i inne metody są już pokryte.

Dobrze, jak więc przekształcić napis na liczbę? Podobnie łatwo. Typ int ma statyczną metodę "Parse()", z której skorzystamy.


using System;

namespace Project1
{
	public class Ex1
	{
		public static void Main(string[] args)
		{
			string  a = "456";
			int     b = int.Parse(a);
			Console.WriteLine( b.ToString() );
		}
	}
}
Tak samo konwertuje się napisy na liczby zmiennoprzecinkowe.

Świetnym pomysłem w C# jest to, że bardzo łatwo w kodzie uzyskać instancję obiektu samego kompilatora, którego można użyć do kompilowania kodu w trakcie pracy programu. Oto przykład prostego kompilatora napisanego w C#. Może tworzyć pliki wykonywalne, może również kompilować kod do pamięci i stamtąd przywoływać metody. W taki sposób można łatwo zmienić C# w język skryptowy.

Przykład jest dość skomplikowany, ponieważ korzysta z przestrzeni nazw System.Windows.Forms, która pozwala tworzyć okna i komponenty wizualne. Proszę zauważyć, że w przeciwieństwie do wielu języków, w C# projekt okna programu i komponentów jest częścią kodu! Środowisko uruchomieniowe Visual .NET Studio potrafi interpretować kod tworzący opis okna i komponentów i wykonywać operacje edycyjne w trybie znanym z innych środowisk typu RAD. To oznacza, że zmiana w kodzie linii:

this.textBox1.Size = new System.Drawing.Size(240, 240);

na

this.textBox1.Size = new System.Drawing.Size(10, 10);


i przejście w tryb projektowania formularzy, spowoduje natychmiastową zmianę rozmiarów pola tekstowego, dokonanie zaś zmian w trybie projektowania formularzy (rozciągnięcie pola tekstowego) i przejście w tryb edycji kodu, spowoduje natychmiastową zmianę treści powyższej linii.
Pełniejszy opis tej przestrzeni nazw znaleźć można w dowolnej książce nt. C# book, na przykład:

/* Wiktor Zychla, 2002 */
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Diagnostics;
using System.Threading;
using System.Reflection;

namespace SimpleCSCompiler
{
	public class Form1 : System.Windows.Forms.Form
	{
		private System.Windows.Forms.TextBox textBox1;
		private System.Windows.Forms.Button button1;
		private System.Windows.Forms.TextBox textBox2;
		private System.Windows.Forms.Button button2;
		private System.Windows.Forms.Button button3;
		private System.ComponentModel.Container components = null;

		public Form1()
		{
			InitializeComponent();
		}

		/// 
		/// Clean up any resources being used.
		/// 
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

      #region Windows Form Designer generated code
		/// 
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// 
		private void InitializeComponent()
		{
			this.textBox2 = new System.Windows.Forms.TextBox();
			this.textBox1 = new System.Windows.Forms.TextBox();
			this.button1 = new System.Windows.Forms.Button();
			this.button2 = new System.Windows.Forms.Button();
			this.button3 = new System.Windows.Forms.Button();
			this.SuspendLayout();
			// 
			// textBox2
			// 
			this.textBox2.BackColor = System.Drawing.SystemColors.Control;
			this.textBox2.BorderStyle = System.Windows.Forms.BorderStyle.None;
			this.textBox2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
			this.textBox2.ForeColor = System.Drawing.SystemColors.WindowText;
			this.textBox2.Location = new System.Drawing.Point(248, 8);
			this.textBox2.Multiline = true;
			this.textBox2.Name = "textBox2";
			this.textBox2.Size = new System.Drawing.Size(240, 240);
			this.textBox2.TabIndex = 2;
			this.textBox2.Text = "";
			// 
			// textBox1
			// 
			this.textBox1.Location = new System.Drawing.Point(4, 8);
			this.textBox1.Multiline = true;
			this.textBox1.Name = "textBox1";
			this.textBox1.Size = new System.Drawing.Size(240, 240);
			this.textBox1.TabIndex = 0;
			this.textBox1.Text = "write the C# code here and use buttons below";
			// 
			// button1
			// 
			this.button1.Location = new System.Drawing.Point(4, 252);
			this.button1.Name = "button1";
			this.button1.TabIndex = 1;
			this.button1.Text = "Build";
			this.button1.Click += new System.EventHandler(this.button1_Click);
			// 
			// button2
			// 
			this.button2.Location = new System.Drawing.Point(84, 252);
			this.button2.Name = "button2";
			this.button2.TabIndex = 1;
			this.button2.Text = "Run";
			this.button2.Click += new System.EventHandler(this.button1_Click);
			// 
			// button3
			// 
			this.button3.Location = new System.Drawing.Point(4, 280);
			this.button3.Name = "button3";
			this.button3.Size = new System.Drawing.Size(76, 36);
			this.button3.TabIndex = 3;
			this.button3.Text = "Run In Memory?";
			this.button3.Click += new System.EventHandler(this.button3_Click);
			// 
			// Form1
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(508, 321);
			this.Controls.AddRange(new System.Windows.Forms.Control[] {
																		  this.button3,
																		  this.button2,
																		  this.textBox2,
																		  this.button1,
																		  this.textBox1});
			this.Name = "Form1";
			this.Opacity = 0.89999997615814209;
			this.Text = "The simple compiler written in C#";
			this.ResumeLayout(false);

		}
      #endregion

		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}

		private void button1_Click(object sender, System.EventArgs e)
		{
			CSharpCodeProvider codeProvider = new CSharpCodeProvider();
			ICodeCompiler icc = codeProvider.CreateCompiler();
			string Output = "Out.exe";
			Button ButtonObject = (Button) sender;

			textBox2.Text = "";
			System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
			parameters.GenerateExecutable = true;
			parameters.OutputAssembly = Output;
			CompilerResults results = icc.CompileAssemblyFromSource(parameters,textBox1.Text);

			if (results.Errors.Count > 0)
			{
				textBox2.ForeColor = Color.Red;
				foreach(CompilerError CompErr in results.Errors)
				{
					textBox2.Text = textBox2.Text +
						"Line number " + CompErr.Line + 
						", Error Number: " + CompErr.ErrorNumber + 
						", '" + CompErr.ErrorText + ";" + 
						Environment.NewLine + Environment.NewLine;
				}
			}
			else
			{
				textBox2.ForeColor = Color.Blue;
				textBox2.Text = "Success!";
				if (ButtonObject.Text == "Run") Process.Start(Output);            
			}
		}

		private void button3_Click(object sender, System.EventArgs e)
		{
			CSharpCodeProvider codeProvider = new CSharpCodeProvider();
			ICodeCompiler icc = codeProvider.CreateCompiler();
			Button ButtonObject = (Button) sender;

			textBox2.Text = "";
			System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
			parameters.GenerateExecutable = false;
			parameters.GenerateInMemory = true;
			CompilerResults results = icc.CompileAssemblyFromSource(parameters,textBox1.Text);

			if (results.Errors.Count > 0)
			{
				textBox2.ForeColor = Color.Red;
				foreach(CompilerError CompErr in results.Errors)
				{
					textBox2.Text = textBox2.Text +
						"Line number " + CompErr.Line + 
						", Error Number: " + CompErr.ErrorNumber + 
						", '" + CompErr.ErrorText + ";" + 
						Environment.NewLine + Environment.NewLine;
				}
			}
			else
			{
				textBox2.ForeColor = Color.Blue;
				textBox2.Text = "Success!";

				Assembly   assembly = results.CompiledAssembly;
				Type   t = assembly.GetType("AXXX.ABC");

				MethodInfo me = t.GetMethod("Alabama");

				/*
				// list all methods
				foreach( MemberInfo mem in t.GetMethods() )
					MessageBox.Show ( mem.Name.ToString() );
				*/

				object result;
				result = t.InvokeMember("Clabama", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod ,
					 null, null, null );				
				MessageBox.Show ( ((int)result).ToString() );
            
			}
		
		}

	}

}

/*
  // test the compiler with this code
using System;

namespace AXXX
{
	class ABC
	{
		public static int[] Alabama()
		{
			int []i = new int[2];
			i[0] = 78;
			return i;
		}
		public static string Blabama()
		{
			return "alamakota";
		}
		public static int Clabama()
		{
			return 56;
		}
	}
}
*/

Jeśli program jest skomplikowany i zawiera wiele plików źródłowych, csc.exe musi otrzymać nazwy wszystkich plików projektu jako parametry. Aby pokonać tę trudność napisałem prosty program, który nazwałem edPCS. Obok plików projektu buduje się prosty plik XML, który przechowuje informacje o projekcie, a następnie uruchamia się edPCS.exe z nazwą pliku XML jako parametrem. Sam używam edPCS.exe z EditPlusem, sprytnym edytorem tekstu. Wystarczy zmapować edPCS.exe jak narzędzie użytkownika, wypełnić strukturę pliku XML i używać edPCS.exe jak kompilatora. Przykład dodatkowo demonstruje jak korzystać z przestrzeni nazw System.XML.XPath, która oferuje nawet prostszy sposób parsowania plików XML niż znane do tej pory obiekty DOM.


	/*
	 * File: edPCS.cs
	 * 
	 * (c) 2002 Wiktor Zychla, torq314@wp.pl
	 * Feel free to modify&develop thit tool.
	 * 
	 * A very simple command line tool that 
	 * helps to compile *.cs files from command line.
	 *  
	 * You no longer need to run csc.exe with tons of parameters, 
	 * instead you run edPCS.exe that 
	 * a) reads a configuration file where you put info about:
	 *    - .NET framework path
	 *    - project path 
	 *    - source files
	 *    - compiler switches
	 * b) invokes csc.exe with a full command line including
	 *    all needed parameters
	 * 
	 * Suppose you'd like to use EditPlus as your C# editor.
	 * You just have to:
	 * 1. compile this source
	 * 2. put the edPCS.exe somewhere
	 * 3. run EditPlus
	 * 4. configure edPCS.exe as a new user tool
	 * 5. create a new project
	 * 6. add a special XML file for the project containing
	 *    enough info to run a compiler
	 * 7. run edPCS.exe from EditPlus on the XML file
	 * 
	 * XML file format:
	 * 
	 * C:\WinNT\Microsoft NET Framework
	 * D:\Project Path
	 * /nologo /target:exe 
	 * file1.cs
	 * file2.cs
	 * file3.cs
	 * 
     */

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.XPath;

namespace edPlusCSharp
{
	class cClass
	{
		static string    sFWPath;
		static string    sProjPath;
		static string    sSwitch;
		static ArrayList sSourceFiles = new ArrayList();

		static void Main(string[] args)
		{
			try
			{
				ParseDataFile( args[0] );			
				string answer = CompileFiles();
				Console.WriteLine ( answer );
			}
			catch ( System.IndexOutOfRangeException ex )
			{
				Console.WriteLine( "A simple tool that helps to compile *.cs files from EditPlus" );  
				Console.WriteLine( "Usage: EXENAME xmldata.xml" );
			}
			catch ( Exception ex )
			{
				Console.WriteLine( "Exception caught. " + ex.Message );
			}
		}

		static void ParseDataFile( String fName )
		{
			try
			{
				XPathDocument xpd   = new XPathDocument( fName );								
				XPathNavigator xpn  = xpd.CreateNavigator();
				
				// framework
				XPathNodeIterator i = xpn.Select("//DATA/FWPath");
				if ( i.MoveNext() )	sFWPath = i.Current.Value;            
				
				// projpath
				i = xpn.Select("//DATA/ProjPath");
				if ( i.MoveNext() )	sProjPath = i.Current.Value;   
         
				// switch
				i = xpn.Select("//DATA/Switch");
				if ( i.MoveNext() )	sSwitch = i.Current.Value;   

				// source files
				i = xpn.Select("//DATA/SourceFile");
				while (i.MoveNext())
					sSourceFiles.Add ( i.Current.Value );
			}
			catch ( Exception ex )
			{
				throw new Exception( "Datafile parse error: " + ex.Message );
			}
		}

		static string CompileFiles()
		{
			string answer = "";
			try
			{
				int i;
				string arg = "";
				
				if ( sSwitch != "" )
					arg += " " + sSwitch; 

				for ( i=0; i<sSourceFiles.Count; i++ ) 
					arg += " \"" + sProjPath + @"\" + sSourceFiles[i] + "\"";

				Process p = new Process();

				p.StartInfo.UseShellExecute = false;
				p.StartInfo.RedirectStandardOutput = true;
				p.StartInfo.FileName  = sFWPath + @"\\csc.exe";
				p.StartInfo.Arguments = arg;
				p.Start(); 
				
				answer = p.StandardOutput.ReadToEnd();
				p.WaitForExit(); 
				
				p.Dispose(); 			
				
				return answer;
			}
			catch ( Exception ex )
			{
				throw new Exception( "Runtime exception: " + ex.Message );
			}
		}
	}
}

Przykładowy projekt XML, edProject.XML:

<DATA>
<FWPath>c:\WINNT\Microsoft.NET\Framework\v1.0.3705</FWPath>
<ProjPath>D:\000</ProjPath>
<Switch>/nologo /target:exe</Switch>
<SourceFile>vic1.cs</SourceFile>
<SourceFile>vic2.cs</SourceFile>
</DATA>


Użyteczne linki na temat C#: