David's profileprowyh's spaceBlogLists Tools Help

Blog


    November 27

    IEnumerable & IEnumerator

    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }
     
    public interface IEnumerator
    {
        bool MoveNext();
        void Reset();
     
        Object Current { get; }
    }
     
    IEnumerable和IEnumerator有什么区别?这是一个很让人困惑的问题(在很多forum里都看到有人在问这个问题)。研究了半天,得到以下几点认识:
     
    1、一个Collection要支持foreach方式的遍历,必须实现IEnumerable接口(亦即,必须以某种方式返回IEnumerator object)。
     
    2、IEnumerator object具体实现了iterator(通过MoveNext(),Reset(),Current)。
     
    3、从这两个接口的用词选择上,也可以看出其不同:IEnumerable是一个声明式的接口,声明实现该接口的class是“可枚举(enumerable)”的,但并没有说明如何实现枚举器(iterator);IEnumerator是一个实现式的接口,IEnumerator object就是一个iterator。
     
    4、IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法建立了连接,client可以通过IEnumerable的GetEnumerator()得到IEnumerator object,在这个意义上,将GetEnumerator()看作IEnumerator object的factory method也未尝不可。
     

    Collection & Iterator in .NET

    在OOP术语中,Collection是指包含其它对象的一种数据结构。C++ STL泛称为Container,.NET Framework泛称为Collection。
     
    Iterator是一种遍历Collection的机制:an iterator is an object which allows a programmer to traverse through all the elements of a collection, regardless of its specific implementation.
     
    Collection提供了聚集对象的容器(Container),Iterator提供了访问容器中对象的统一方法,Collection与Iterator两者结合,为开发人员提供了表示、操纵各种复杂数据结构的强大工具。
     
    例一:Collection与Iterator在C# 1.0中的实现

    using System;
    using System.Collections;

    namespace Levensoft
    {
        // data object contained in PeopleCollection
        public class Person
        {
            public Person(string name, int age)
            {
                this.name = name;
                this.age = age;
            }
            
            public string Name
            {
                get { return name; }
            }
            public int Age
            {
                get { return age; }
            }

            private string name;
            private int age;
        }

        public class PeopleCollection : IEnumerable
        {
            // IEnumerable
            public IEnumerator GetEnumerator()
            {
                return new PeopleCollectionEnum(_peopleCollection);
            }

            // IEnumerator
            private class PeopleCollectionEnum : IEnumerator
            {
                private Person[] _peopleCollection;
                private int _position = -1;

                public PeopleCollectionEnum(Person[] list)
                {
                    _peopleCollection = list;
                }

                public bool MoveNext()
                {
                    _position++;

                    return (_position < _peopleCollection.Length);
                }
                public void Reset()
                {
                    _position = -1;
                }
                public object Current
                {
                    get
                    {
                        try
                        {
                            return _peopleCollection[_position];
                        }
                        catch (IndexOutOfRangeException)
                        {
                            throw new InvalidOperationException();
                        }
                    }
                }
            }

            public PeopleCollection()
            {
                _peopleCollection = new Person[3]
                {
                    new Person("Bob", 30),
                    new Person("Jim", 31),
                    new Person("Sue", 29),
                };
            }

            // Indexer & Length provide the ability of Array-like accessor
            public Person this[int index]
            {
                get
                {
                    try
                    {
                        return _peopleCollection[index];
                    }
                    catch (IndexOutOfRangeException) { throw; }
                }
            }

            public int Length
            {
                get
                {
                    return _peopleCollection.Length;
                }
            }

            private Person[] _peopleCollection;
        }

        public class test
        {
            public static void Main()
            {
                PeopleCollection peopleCollection = new PeopleCollection();

                // IEnumerable & IEnumerator enable foreach statement
                foreach (Person p in peopleCollection)
                {
                    Console.WriteLine(p.Name + "\t\t" + p.Age);
                }

                // Indexer & Length enable for statement
                for (int i = 0; i < peopleCollection.Length; i++)
                {
                    Console.WriteLine(peopleCollection[i].Name + "\t\t" + peopleCollection[i].Age);
                }
            }
        }
    }

    例二:Collection & Iterator在C# 2.0中的实现

    using System;
    using System.Collections;

    namespace Levensoft
    {
        public class Person
        {
            public Person(string name, int age)
            {
                this.name = name;
                this.age = age;
            }
            
            public string Name
            {
                get { return name; }
            }
            public int Age
            {
                get { return age; }
            }

            private string name;
            private int age;
        }

        public class PeopleCollection : IEnumerable
        {
            public IEnumerator GetEnumerator()
            {
                // iterator block
                for (int i = 0; i < _peopleCollection.Length; i++)
                {
                    yield return _peopleCollection[i];
                }
            }

            public PeopleCollection()
            {
                _peopleCollection = new Person[3]
                {
                    new Person("Bob", 30),
                    new Person("Jim", 31),
                    new Person("Sue", 29),
                };
            }

            public Person this[int index]
            {
                get
                {
                    try
                    {
                        return _peopleCollection[index];
                    }
                    catch (IndexOutOfRangeException) { throw; }
                }
            }

            public int Length
            {
                get
                {
                    return _peopleCollection.Length;
                }
            }

            private Person[] _peopleCollection;
        }

        public class test
        {
            public static void Main()
            {
                PeopleCollection peopleCollection = new PeopleCollection();

                foreach (Person p in peopleCollection)
                {
                    Console.WriteLine(p.Name + "\t\t" + p.Age);
                }

                for (int i = 0; i < peopleCollection.Length; i++)
                {
                    Console.WriteLine(peopleCollection[i].Name + "\t\t" + peopleCollection[i].Age);
                }
            }
        }
    }

    从例二可以看出,C# 2.0通过iterator block大大简化了Collection的遍历机制(iterator)。实际上,iterator block是C# 2.0为程序员提供的一种便利设施(handy facility),C#编译器在编译时,仍然生成如例一所示的enumerator class。

     

    November 26

    Thread & Thread ApartmentState

    Apartment是COM的概念,在.NET中本不涉及Apartment,但如果需要与COM互操作(COM Interop),则需要适当设置.NET线程的ApartmentState。
        
    1、Attribute设置只适用于入口点方法(entry method)如Main(),对其它方法无意义;
        
    2、获取ApartmentState使用GetApartmentState(),设置ApartmentState使用SetApartmentState(ApartmentState.Enum)。
        
    示例如下:
        
    using System;
    using System.Threading;
     
    namespace Levensoft
    {
        public class ThreadApartmentState
        {
            public static void Main()
            {
                Thread t = new Thread(new ThreadStart(ThreadTest));
                Console.WriteLine("t.ApartmentState: " + t.GetApartmentState().ToString());
     
                t.SetApartmentState(ApartmentState.STA);
                Console.WriteLine("t.ApartmentState: " + t.GetApartmentState().ToString());
     
                t.Start();
            }
     
            public static void ThreadTest()
            {
                Thread tt = Thread.CurrentThread;
                Console.WriteLine("tt.ApartmentState: " + tt.GetApartmentState().ToString());
            }
        }
    }

    此例的输出:
    t.ApartmentState: Unkown
    t.ApartmentState: STA
    tt.ApartmentState: STA
     
    通过此例归纳出的简单结论:
     
    1、通过new Thread()创建的线程,初始ApartmentState为Unkown;
     
    2、可以通过SetApartmentState()设置线程的ApartmentState为STA或MTA;
     
    3、线程的ApartmentState只可以设置一次,而且是在调用Start()启动线程之前设置。
     
    4、线程的ApartmentState只能在其创建者中设置,而不能在线程本身的代码中设置,因为线程代码的执行是由Start()启动的。
     
    November 20

    .NET Configuration

    .NET Configuration对于系统及应用程序的运行环境配置提供了极大的灵活性,但由于版本的关系,配置信息的表述和读取显得相当混乱,许多文章甚至书籍中给出的示例代码都不能在.NET 2.0下运行,下面给出一个可以运行的例子。
     
    1、config文件片断
     
    <configuration>
        <appSettings>
            <add key="logfile" value="C:\logfile\event.log" />
        </appSettings>
     
        <connectionStrings>
            <add name="myConnString"
                     providerName="System.Data.SqlClient"
                     connectionString="Server=(local);Database=mydb;uid=sa;pwd=mypass" />
        </connectionStrings>
    </configuration>
     
    此config文件片断定义了两项内容:一是logfile文件名,二是数据库连接字符串。
     
    对于Web Forms应用,将此文件命名为web.config并存于网站的根目录下。
     
    对于Windows Forms应用,将此文件命名为app.config并存于应用程序的根目录下。如程序名为wordpad.exe,则config文件为wordpad.exe.config,且与wordpad.exe位于同一目录下。
     
    2、Web Forms应用中对配置信息的读取
     
    下面的代码读入上述config文件中定义的内容:
     
    // open config file
    Configuration config = WebConfigurationManager.OpenWebConfiguration("/");
     
    // get logfile
    AppSettingsSection appSettings = config.AppSettings as AppSettingsSection;
    string logfile = appSettings.Settings["logfile"].Value;
     
    // get connectionString
    ConnectionStringsSection cnstrSection = config.ConnectionStrings;
    ConnectionStringSettingsCollection cnstrCollection = cnstrSection.ConnectionStrings;
    ConnectionStringSettings cnstrSettings = cnstrCollection["myConnString"];
    string connectionString = cnstrSettings.ConnectionString;
     
    3、Windows Forms应用中对配置信息的读取
     
    Windows Forms读取config文件的代码,除了打开文件的代码不同之外,其余与Web Forms中的读取代码是一样的。下面给出一种简化的读取代码:
     
    // open config file
    Configuration config = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
     
    // get logfile
    AppSettingsSection appSettings = config.AppSettings as AppSettingsSection;
    string logfile = appSettings.Settings["logfile"].Value;
     
    // get connectionString
    string connectionString = config.ConnectionStrings.ConnectionStrings["myConnString"].ConnectionString;
     
    .NET Configuration极其灵活,这里给出的只是一个非常简单的例子,深入研究容待后续。 
    November 07

    Simplified InstallUtil.exe

    下面的代码演示了InstallUtil.exe的实现原理:
        
    //////////////////////////////////
    // SimpleInstall.cs
    //    Simplified InstallUtil.exe
    //////////////////////////////////
    using System;
    using System.ComponentModel;
    using System.Collections;
    using System.Configuration.Install;
    using System.IO;
     
    namespace SimpleInstallUtil
    {
        public class SimpleInstall
        {
            public static void Main(string[] args)
            {
                ArrayList options = new ArrayList();
                string myOption;
                bool toUninstall = false;
                bool toPrintHelp = false;
                TransactedInstaller transactedInstaller = new TransactedInstaller();
                AssemblyInstaller assemblyInstaller;
                InstallContext installContext;
     
                try
                {
                    for (int i = 0; i < args.Length; i++)
                    {
                        // process the arguments
                        if (args[i].StartsWith("/") || args[i].StartsWith("-"))
                        {
                            // options
                            myOption = args[i].Substring(1);
     
                            // which option ?
                            if (String.Compare(myOption, "u", true) == 0 || String.Compare(myOption, "uninstall", true) == 0)
                            {
                                toUninstall = true;
                                continue;
                            }
                            if (String.Compare(myOption, "?", true) == 0 || String.Compare(myOption, "help", true) == 0)
                            {
                                toPrintHelp = true;
                                continue;
                            }
     
                            // other options, save it
                            options.Add(myOption);
                        }
                        else
                        {
                            // assembly to be installed
                            if (!File.Exists(args[i]))
                            {
                                Console.WriteLine("Error: {0} - Assembly file doesn't exist.", args[i]);
                                return;
                            }
     
                            // create an instance of 'AssemblyInstaller' that installs the given assembly
                            assemblyInstaller = new AssemblyInstaller(args[i], (string[])options.ToArray(typeof(string)));
     
                            // add the instance of 'AssemblyInstaller' to the 'TransactedInstaller'
                            transactedInstaller.Installers.Add(assemblyInstaller);
                        }
                    }
     
                    // help ?
                    if (toPrintHelp || transactedInstaller.Installers.Count == 0)
                    {
                        PrintHelp();
                        return;
                    }
     
                    installContext = new InstallContext("SimpleInstall.log", (string[])options.ToArray(typeof(string)));
                    transactedInstaller.Context = installContext;
     
                    // install Or uninstall ?
                    if (!toUninstall)
                    {
                        transactedInstaller.Install(new Hashtable());
                    }
                    else
                    {
                        transactedInstaller.Uninstall(null);
                    }
                }
                catch (Exception exp)
                {
                    Console.WriteLine("Exception: {0}", exp.Message.ToString());
                }
            }
     
            public static void PrintHelp()
            {
                Console.WriteLine("Usage: SimpleInstall [/u | /uninstall] [option [...]] assembly [[option [...]] assembly [...]]");
                Console.WriteLine("SimpleInstall executes the installers in each of the given assembly.");
                Console.WriteLine("If /u or /uninstall option is given it uninstalls the assemblies.");
            }
        }
    }
     
    由此可见,InstallUtil.exe的实现原理是比较简单的:
     
    1、分析命令行参数;
    2、构造AssemblyInstaller;
    3、由AssemblyInstaller构造TransactedInstaller;
    4、调用TransactedInstaller的Install或Uninstall方法完成安装或卸载。
     
    由此代码可以体会OOP的美妙之处:AssemblyInstaller和TransactedInstaller构成了InstallUtil.exe的核心,InstallUtil.exe只需要构造the instance of TransactedInstaller,然后调用其方法即可。而TransactedInstaller实例由多个AssemblyInstaller实例构成,所以任务归结为构造AssemblyInstaller的实例。而AssemblyInstaller实例的构造通过解析命令行参数完成。
    November 06

    Installer Tools (installutil.exe)

    .NET Framework提供了InstallUtil.exe安装工具用于某些应用程序的安装。
     
    某些服务器应用程序除了传统的程序文件之外,还关联了一些资源,如消息队列(message queues)、事件日志(event logs)、性能计数器(performance counters)等,在部署应用程序时,必须创建这些相关资源,而在卸载应用程序时,也必须同时删除这些资源。.NET Framework提供了安装组件(installer components)用于完成这些任务。
     
    Installer components是导出自System.Configuration.Install.Installer的用户自定义Class,带有[RunInstaller(true)] attribute:
     
    [RunInstaller(true)]
    public class MyInstaller
    {
        // .....
    }
     
    System.Configuration.Install.Installer类实现了四个方法:Install, Commit, Rollback, Uninstall,用于实现事务性的(transacted)安装/卸载功能。
     
    InstallUtil.exe安装工具通过反射方法获取要安装的程序集的installer component,调用其中的Install进行安装,如果Install执行成功,则最后执行Commit提交安装;如果在执行Install的过程中出现错误,则执行Rollback完成回滚,使系统恢复到安装前的状态。
     
    InstallUtil.exe /u 调用Uninstall方法完成卸载。注意:卸载是非事务性的,亦即如果卸载过程中出现错误,则忽略,继续卸载其余部分。
     
    InstallUtil.exe安装完成后,在当前目录产生三个文件:
    • InstallUtil.InstallLog
      该文件含有安装过程的描述。
    • assemblyname.InstallLog
      该文件含有安装过程的Commit阶段的信息。
    • assemblyname.InstallState
      该文件含有卸载时需要的信息。注意:卸载应用程序时,当前目录必须含有该文件,否则卸载失败。

    由此简单说明可以看出,.NET Framework通过InstallUtil.exe/Installer Components对服务器端应用程序提供了一个框架性的安装工具,方便了Windows Service这类服务器端应用程序的开发。

    下面的例子演示了Installer Class的用法:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Configuration.Install;

    namespace InstallerDemo
    {
        // RunInstallerAttribute declares MyInstaller class is an installer component.
        [RunInstaller(true)]
        public class MyInstaller : Installer
        {
            public MyInstaller() : base()
            {
                // Attach the 'BeforeInstall' event
                this.BeforeInstall += MyInstallerBeforeInstall;

                // Attach the 'AfterInstall' event
                this.AfterInstall += MyInstallerAfterInstall;

                // Attach the 'Committed' event
                this.Committed += MyInstallerCommitted;

                // Attach the 'Committing' event
                this.Committing += MyInstallerCommitting;
            }

            // Event handler for 'BeforeInstall'
            private void MyInstallerBeforeInstall(object sender, InstallEventArgs args)
            {
                Console.WriteLine("Before install...");
            }

            // Event handler for 'AfterInstall'
            private void MyInstallerAfterInstall(object sender, InstallEventArgs args)
            {
                Console.WriteLine("After install.");
            }

            // Event handler for 'Committed'
            private void MyInstallerCommitted(object sender, InstallEventArgs args)
            {
                Console.WriteLine("Installer committed.");
            }
            // Event handler for 'Committing'
            private void MyInstallerCommitting(object sender, InstallEventArgs args)
            {
                Console.WriteLine("Installer committing...");
            }

            // Override the 'Install' method
            public override void Install(IDictionary savedState)
            {
                base.Install(savedState);
                Console.WriteLine("Installing...");
            }

            // Override the 'Commit' method
            public override void Commit(IDictionary savedState)
            {
                base.Commit(savedState);
                Console.WriteLine("Committing...");
            }

            // Override the 'Rollback' method
            public override void Rollback(IDictionary savedState)
            {
                base.Rollback(savedState);
                Console.WriteLine("Rolling back...");
            }

            public static void Main()
            {
                // ......
            }
        }
    }

    例子中注册事件处理的代码利用了C#的简化语法:

    this.BeforeInstall += MyInstallerBeforeInstall;

    完整的语法应该是:

    this.BeforeInstall += new InstallEventHandler(MyInstallerBeforeInstall);

    由此可见,C# syntactical sugar为开发人员(特别是熟悉C/C++的开发人员)提供了莫大的便利,使得代码更加自然与直观。

    上述的InstallEventHandler的声明是:

    public delegate void InstallEventHandler(Object sender, InstallEventArgs args);

    根据此delegate的声明,可知MyInstallerBeforeInstall的声明必须是:

    void MyInstallerBeforeInstall(Object sender, InstallEventArgs args);

    Windows Service with C#

    最近需要用C#写一个Windows Service(以前称为NT Service),Google了一下,发现很多相关文章都是依据Visual Studio .NET的,但因为VS .NET隐含了很多细节,对我们搞清Windows Service with C#的实现机制没有多大帮助。下面给出一个手写的Windows Service with C#的skeleton:
        
    // file: cservice.cs                
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.ServiceProcess;
    using System.Configuration.Install;
     
    namespace Levensoft.Service.WindowsService
    {
        // Windows Service class
        public class MyFirstService : System.ServiceProcess.ServiceBase
        {
            public MyFirstService()
            {
                this.CanPauseAndContinue = true;
                this.CanShutdown = true;
                this.CanHandleSessionChangeEvent = false;
     
                this.ServiceName = "MyFirstService";
            }
     
            protected override void OnStart(string[] args)
            {
            }
            protected override void OnStop()
            {
            }
            protected override void OnContinue()
            {
            }
     
            public static void Main()
            {
                ServiceBase[] servicesToRun;
                servicesToRun = new ServiceBase[] {new Levensoft.Service.WindowsService.MyFirstService()};
                ServiceBase.Run(servicesToRun);
            }
        }
     
        // Windows Service Installer Components
        [RunInstaller(true)]
        public class ProjectInstaller : System.Configuration.Install.Installer
        {
            private ServiceProcessInstaller myServiceProcessInstaller;
            private ServiceInstaller myServiceInstaller;
     
            public ProjectInstaller()
            {
                this.myServiceProcessInstaller = new ServiceProcessInstaller();
                this.myServiceInstaller = new ServiceInstaller();
     
                // myServiceProcessInstaller
                // Account or Username & Password
                this.myServiceProcessInstaller.Account = ServiceAccount.LocalSystem;
                this.myServiceProcessInstaller.Username = null;
                this.myServiceProcessInstaller.Password = null;
     
                // myServiceInstaller
                // ServiceName & StartType
                this.myServiceInstaller.ServiceName = "MyFirstService";
                this.myServiceInstaller.StartType = ServiceStartMode.Automatic;
     
                // ProjectInstaller
                this.Installers.AddRange(new Installer[] {this.myServiceProcessInstaller, this.myServiceInstaller});
            }
        }
    }
        
    1、编译
        
    csc cservice.cs
        
    2、安装
        
    installutil cservice.exe
        
    3、启动服务
        
    net start MyFirstService
        
    4、停止服务
        
    net stop MyFirstService
        
    5、卸载
        
    installutil /u cservice.exe