Sunday, April 19, 2009

Reading from ini file and subscribing to Add/Edit/Delete events

Stumble del.icio.us Reddit MyWeb! Facebook Google bookmark

I'm sure quite a few of you would have come across a situation when you have to read certain user defined variable field values from an ini (or text) file based on which your application will have to do something. An example could be a windows service which takes the server name/port to which it has to connect to as soon as it is started or the database server name to which a service has to connect to. Or for example a service which keeps listening on a serial port and which takes the various parameters to connect to serial port from an ini file. In such cases, where your application requires to read from an ini file, the following code will help you take care of any change (add/edit/delete etc) that occurs to the ini file. It also demonstrates how to read section/field name/values from ini file. The comments are elaborate and so I assume the code should be a cakewalk to understand...


Here is the base class to represent the ini file...

    1 // Copyright © 2009 Yours truly.  All rights reserved.

    2 

    3 using System;

    4 using System.Collections.Generic;

    5 using System.Diagnostics;

    6 using System.IO;

    7 using System.Globalization;

    8 using System.Text;

    9 

   10 namespace MyIniFileReader

   11 {

   12     //**************************************************************************

   13     /// <summary>

   14     /// Encapsulates a Windows INI file, which is a text file containing

   15     /// key=value pairs organized into sections denoted by strings enclosed in

   16     /// square brackets (e.g., [Section]).

   17     /// </summary>

   18     /// <remarks>

   19     /// See the MSDN documentation of the GetProfileString() function

   20     /// (http://msdn.microsoft.com/en-us/library/ms724366.aspx) for information

   21     /// on the format of the file this class supports.

   22     /// <para>To read an INI file, construct a new instance and call the

   23     /// <see cref="Load"/> method.  Then, set the <see cref="CurrentSection"/>

   24     /// and obtain values by calling one of the Read*() methods (e.g.

   25     /// <see cref="ReadString(string)"/>.</para>

   26     /// <para>This class only supports reading from an INI file.

   27     /// TODO: Add the capability to write an INI file.</para>

   28     /// <para>The registry mapping capability of the GetProfileString() function

   29     /// is not supported by this implementation.</para>

   30     /// <para>$Id$</para>

   31     /// <author>Author: Yours truly</author>

   32     /// </remarks>

   33     /// <threadsafety static="true" instance="false"/>

   34     //**************************************************************************

   35     public class MyBaseIniFile

   36     {

   37         /// <summary>The delimiters separating key from value.</summary>

   38         private static readonly char[] DELIM_KEY_VALUE = new char[] { '=' };

   39         /// <summary>Holds the <see cref="Sections"/>.</summary>

   40         private readonly Dictionary<string, IniSection> m_sections =

   41             new Dictionary<string, IniSection>();

   42         /// <summary>Holds the <see cref="CurrentSection"/>.</summary>

   43         private string m_currentSection = String.Empty;

   44 

   45 

   46         //**********************************************************************

   47         /// <summary>

   48         /// Construct a new instance with no sections and no data.

   49         /// </summary>

   50         public MyBaseIniFile()

   51         {

   52         }

   53 

   54 

   55         //**********************************************************************

   56         /// <summary>

   57         /// Gets or sets the section from which values are read, and to which

   58         /// values are written.

   59         /// </summary>

   60         /// <value>Defaults to <see cref="String.Empty">the empty string</see>.

   61         /// Will never be <see langword="null"/>.</value>

   62         /// <remarks>

   63         /// When set to an empty string, two distinct locations are identified:

   64         /// The first is the area preceding the start of the first section.  The

   65         /// second is the empty name section.  This section's name is an empty

   66         /// string (i.e., "[]" or the "" section).

   67         /// </remarks>

   68         /// <exception cref="ArgumentNullException">An attempt was made to set

   69         /// this property to <see langword="null"/>.</exception>

   70         public string CurrentSection

   71         {

   72             get { return m_currentSection; }

   73 

   74             set

   75             {

   76                 if (value == null) throw new ArgumentNullException("value");

   77                 m_currentSection = value;

   78             }

   79         }

   80 

   81         //**********************************************************************

   82         /// <summary>

   83         /// Gets a map of objects containing the key=value pairs for each

   84         /// section, keyed by the section name.

   85         /// </summary>

   86         protected IDictionary<string, IniSection> Sections

   87         {

   88             get { return m_sections; }

   89         }

   90 

   91 

   92         //**********************************************************************

   93         /// <summary>

   94         /// Reads the information from the indicated INI file into the current

   95         /// instance.

   96         /// </summary>

   97         /// <remarks>

   98         /// This method infers the encoding from the bytes present at the

   99         /// beginning of the file.  It automatically recognizes UTF-8, UTF-16 LE

  100         /// and UTF-16 BE if the file starts with the appropriate preamble

  101         /// (a.k.a., byte order marks).  If it does not recognize the preamble,

  102         /// it assumes the encoding is UTF-8.

  103         /// <para>When the loading process encounters a duplicate section, it

  104         /// merges its contents with the existing definitions.  Any duplicates

  105         /// (i.e., keys with the same name) are overwritten.  Duplicates

  106         /// encountered later in the file overwrite previous definitions.</para>

  107         /// <para>If the loading process encounters key=value pairs above the

  108         /// first section, it adds them to a section named "" (empty string).

  109         /// This is also referred to as "[]", the "" section and the empty name

  110         /// section.  If there is an explicit "" section defined, any key=value

  111         /// pairs defined there are handled as though they are defined in a

  112         /// duplicate section.</para>

  113         /// </remarks>

  114         /// <param name="path">The path to the INI file.  If a relative path is

  115         /// specified, it is assumed to be relative to the current working

  116         /// directory of the executing process.</param>

  117         /// <returns>

  118         /// <see langword="true"/> if the indicated file was found and loaded;

  119         /// <see langword="false"/> if the indicated file cannot be found.

  120         /// </returns>

  121         /// <exception cref="ArgumentNullException"><paramref name="path"/> is

  122         /// <see langword="null"/>.</exception>

  123         /// <exception cref="ArgumentException"><paramref name="path"/> is an

  124         /// empty string.</exception>

  125         /// <exception cref="IOException"><paramref name="path"/> includes an

  126         /// incorrect or invalid syntax for file name, directory name, or volume

  127         /// label.</exception>

  128         public virtual bool Load(string path)

  129         {

  130             if (path == null) throw new ArgumentNullException("path");

  131 

  132             IDictionary<string, IniSection> sections = Sections;

  133             sections.Clear();

  134 

  135             bool result;

  136             try

  137             {

  138                 ReadIniFile(path, sections);

  139                 result = true;

  140             }

  141             catch (DirectoryNotFoundException)

  142             {

  143                 result = false;

  144             }

  145             catch (FileNotFoundException)

  146             {

  147                 result = false;

  148             }

  149 

  150             return (result);

  151         }

  152 

  153         //**********************************************************************

  154         /// <overloads>

  155         /// Save the information contained in the current instance to the

  156         /// indicated file.

  157         /// </overloads>

  158         /// <summary>

  159         /// Save the information contained in the current instance to the

  160         /// indicated file using UTF-8 encoding.

  161         /// </summary>

  162         /// <param name="path">The path to the INI file.</param>

  163         /// <exception cref="NotImplementedException">This method is not yet

  164         /// implemented.</exception>

  165         public virtual void Save(string path)

  166         {

  167             Save(path, Encoding.UTF8);

  168         }

  169 

  170         //**********************************************************************

  171         /// <summary>

  172         /// Save the information contained in the current instance to the

  173         /// indicated file using the specified encoding.

  174         /// </summary>

  175         /// <param name="path">The path to the INI file.</param>

  176         /// <param name="encoding">The desired encoding.</param>

  177         /// <exception cref="NotImplementedException">This method is not yet

  178         /// implemented.</exception>

  179         public virtual void Save(string path, Encoding encoding)

  180         {

  181             throw new NotImplementedException();

  182         }

  183 

  184         //**********************************************************************

  185         /// <summary>

  186         /// Retrieves a list of strings identifying the sections contained

  187         /// within the current instance.

  188         /// </summary>

  189         /// <returns>

  190         /// A list of zero or more section names.

  191         /// </returns>

  192         public virtual IList<string> ListSections()

  193         {

  194             IDictionary<string, IniSection> sections = Sections;

  195 

  196             List<string> list = new List<string>(sections.Count);

  197             foreach (string name in sections.Keys)

  198             {

  199                 list.Add(name);

  200             }

  201 

  202             return (list);

  203         }

  204 

  205         //**********************************************************************

  206         /// <summary>

  207         /// Retrieves a list of strings identifying the keys defined by the

  208         /// <see cref="CurrentSection">current section</see> contained within

  209         /// the current instance.

  210         /// </summary>

  211         /// <returns>

  212         /// A list of zero or more key names.

  213         /// </returns>

  214         public virtual IList<string> ListKeys()

  215         {

  216             IniSection section = GetCurrentSection();

  217             IList<string> keys;

  218             if (section != null)

  219             {

  220                 IDictionary<string, string> contents = section.Contents;

  221                 keys = new List<string>(contents.Count);

  222                 foreach (string key in contents.Keys)

  223                 {

  224                     keys.Add(key);

  225                 }

  226             }

  227             else

  228             {

  229                 keys = new string[0];

  230             }

  231 

  232             return (keys);

  233         }

  234 

  235         //**********************************************************************

  236         /// <summary>

  237         /// Deletes all data contained within the current instance and sets the

  238         /// <see cref="CurrentSection">current section</see> to an empty string.

  239         /// </summary>

  240         public virtual void Clear()

  241         {

  242             Sections.Clear();

  243             CurrentSection = String.Empty;

  244             // Not caught: ArgumentNullException: Can't happen as coded.

  245         }

  246 

  247         //**********************************************************************

  248         /// <overloads>

  249         /// Retrieves the value associated with the given key defined in the

  250         /// <see cref="CurrentSection">current section</see> of this instance.

  251         /// </overloads>

  252         /// <summary>

  253         /// Retrieves the value associated with the given key, or returns

  254         /// <see langword="null"/> if the key (or <see cref="CurrentSection"/>)

  255         /// could not be found.

  256         /// </summary>

  257         /// <param name="key">The key.  An empty string is valid.</param>

  258         /// <returns>

  259         /// The value associated with the given key in the current section, or

  260         /// <see langword="null"/> if the key or section does not exist in the

  261         /// current instance.

  262         /// </returns>

  263         /// <exception cref="ArgumentNullException">The given key was

  264         /// null.</exception>

  265         public virtual string ReadString(string key)

  266         {

  267             if (key == null) throw new ArgumentNullException("key");

  268 

  269             IniSection section = GetCurrentSection();

  270             if ((section != null) && section.Contents.ContainsKey(key))

  271             {

  272                 return (section.Contents[key]);

  273             }

  274             else

  275             {

  276                 return (null);

  277             }

  278         }

  279 

  280         //**********************************************************************

  281         /// <summary>

  282         /// Retrieves the value associated with the given key, or returns the

  283         /// provided default if the key (or <see cref="CurrentSection"/>) could

  284         /// not be found.

  285         /// </summary>

  286         /// <param name="key">The key.  An empty string is valid.</param>

  287         /// <param name="dflt">The value to return if the <paramref name="key"/>

  288         /// (or <see cref="CurrentSection"/>) could not be found.</param>

  289         /// <returns>

  290         /// The value associated with the given key in the current section, or

  291         /// <paramref name="dflt"/> if the key or section does not exist in the

  292         /// current instance.

  293         /// </returns>

  294         /// <exception cref="ArgumentNullException">The given key was

  295         /// null.</exception>

  296         public virtual string ReadString(string key, string dflt)

  297         {

  298             string val = ReadString(key);

  299             if (val == null) val = dflt;

  300             return (val);

  301         }

  302 

  303         //**********************************************************************

  304         /// <overloads>

  305         /// Retrieves the value associated with the given key defined in the

  306         /// <see cref="CurrentSection">current section</see> of this instance

  307         /// and parses it into a 32-bit integer.

  308         /// </overloads>

  309         /// <summary>

  310         /// Retrieves the int value associated with the given key, or throws an

  311         /// ArgumentException if the key (or <see cref="CurrentSection"/>)

  312         /// could not be found.

  313         /// </summary>

  314         /// <param name="key">The key.  An empty string is valid.</param>

  315         /// <returns>

  316         /// The int value associated with the given key in the current section.

  317         /// </returns>

  318         /// <exception cref="ArgumentNullException">The given key was

  319         /// null.</exception>

  320         /// <exception cref="ArgumentException">The given key (or

  321         /// <see cref="CurrentSection"/>) could not be found.</exception>

  322         /// <exception cref="FormatException">The value associated with the

  323         /// given key (and <see cref="CurrentSection"/>) was not in a format

  324         /// that could be parsed into a 32-bit integer.</exception>

  325         /// <exception cref="OverflowException">The value associated with the

  326         /// given key (and <see cref="CurrentSection"/>) was too big or too

  327         /// small to fit in a 32-bit integer.</exception>

  328         public virtual int ReadInt(string key)

  329         {

  330             MyTuple<bool, int> result = ReadIntImpl(key);

  331             if (!result.First)

  332             {

  333                 throw new ArgumentException(

  334                     String.Format(CultureInfo.InvariantCulture,

  335                         "Section '{0}' or key '{1}' was not found",

  336                         CurrentSection, key), "key");

  337             }

  338 

  339             return (result.Second);

  340         }

  341 

  342         //**********************************************************************

  343         /// <summary>

  344         /// Retrieves the int value associated with the given key, or returns

  345         /// the provided default if the key (or <see cref="CurrentSection"/>)

  346         /// could not be found or if the value could not be parsed into a 32-bit

  347         /// integer.

  348         /// </summary>

  349         /// <param name="key">The key.  An empty string is valid.</param>

  350         /// <param name="dflt">The value to return if the <paramref name="key"/>

  351         /// (or <see cref="CurrentSection"/>) could not be found, or the value

  352         /// could not be parsed into a 32-bit integer.</param>

  353         /// <returns>

  354         /// The int value associated with the given key in the current section

  355         /// or <paramref name="dflt"/> if the key or section does not exist in

  356         /// the current instance, or if the value could not be parsed into a

  357         /// 32-bit integer.</returns>

  358         /// <exception cref="ArgumentNullException">The given key was

  359         /// null.</exception>

  360         public virtual int ReadInt(string key, int dflt)

  361         {

  362             int val;

  363             try

  364             {

  365                 MyTuple<bool, int> result = ReadIntImpl(key);

  366                 if (result.First) val = result.Second;

  367                 else val = dflt;

  368             }

  369             catch (FormatException)

  370             {

  371                 val = dflt;

  372             }

  373             catch (OverflowException)

  374             {

  375                 val = dflt;

  376             }

  377 

  378             return (val);

  379         }

  380 

  381 

  382         //**********************************************************************

  383         /// <summary>

  384         /// Attempts to read the value associated with the given key and, if

  385         /// successful, parses it into a 32-bit integer.

  386         /// </summary>

  387         /// <param name="key">The key.  An empty string is valid.</param>

  388         /// <returns>

  389         /// An <see cref="MyTuple{F,S}"/> structure indicating the result of the

  390         /// operation in its <see cref="MyTuple{F,S}.First"/> property and the

  391         /// parsed integer in its <see cref="MyTuple{F,S}.Second"/> property.

  392         /// <para><see cref="MyTuple{F,S}.First"/> is <see langword="true"/> if

  393         /// the <paramref name="key"/> was found in the section indicated by the

  394         /// <see cref="CurrentSection"/> property.  In this case, the <see cref=

  395         /// "MyTuple{F,S}.Second"/> property contains the result of parsing the

  396         /// string into a 32-bit integer.  If the <paramref name="key"/> cannot

  397         /// be found, <see cref="MyTuple{F,S}.First"/> will be <see langword=

  398         /// "false"/> and <see cref="MyTuple{F,S}.Second"/> will be 0.</para>

  399         /// </returns>

  400         /// <exception cref="ArgumentNullException">The given key was

  401         /// <see langword="null"/>.</exception>

  402         /// <exception cref="FormatException">The value associated with the

  403         /// given key (and <see cref="CurrentSection"/>) was not in a format

  404         /// that could be parsed into a 32-bit integer.</exception>

  405         /// <exception cref="OverflowException">The value associated with the

  406         /// given key (and <see cref="CurrentSection"/>) was too big or too

  407         /// small to fit in a 32-bit integer.</exception>

  408         protected MyTuple<bool, int> ReadIntImpl(string key)

  409         {

  410             MyTuple<bool, int> result;

  411 

  412             string valStr = ReadString(key);

  413             if (valStr != null)

  414             {

  415                 // TODO: Use CultureInfo.InvariantCulture?

  416                 int parsed = Int32.Parse(valStr, CultureInfo.CurrentCulture);

  417                 result = new MyTuple<bool, int>(true, parsed);

  418             }

  419             else

  420             {

  421                 result = new MyTuple<bool, int>(false, 0);

  422             }

  423 

  424             return (result);

  425         }

  426 

  427 

  428         //**********************************************************************

  429         /// <summary>

  430         /// Opens, reads and parses the contents of the indicated file, adding

  431         /// the parsed sections to the provided dictionary.

  432         /// </summary>

  433         /// <param name="path">The path to the file to read and parse.  If a

  434         /// relative path is specified, it is assumed to be relative to the

  435         /// current working directory of the executing process.</param>

  436         /// <param name="sections">A dictionary of objects containing key=value

  437         /// pairs defined within a section, keyed by the section name.</param>

  438         /// <exception cref="ArgumentNullException"><paramref name="path"/> is

  439         /// <see langword="null"/>.</exception>

  440         /// <exception cref="ArgumentException"><paramref name="path"/> is an

  441         /// empty string.</exception>

  442         /// <exception cref="DirectoryNotFoundException">The specified path is

  443         /// invalid, such as being on an unmapped drive.</exception>

  444         /// <exception cref="FileNotFoundException">The file identified by

  445         /// <paramref name="path"/> cannot be found.</exception>

  446         /// <exception cref="IOException"><paramref name="path"/> includes an

  447         /// incorrect or invalid syntax for file name, directory name, or volume

  448         /// label.</exception>

  449         private static void ReadIniFile

  450         (

  451             string path,

  452             IDictionary<string, IniSection> sections

  453         )

  454         {

  455             TextReader reader = new StreamReader(path, true);

  456             using (reader)

  457             {

  458                 IniSection iniSection = new IniSection();

  459                 iniSection.Name = String.Empty;

  460 

  461                 string line = reader.ReadLine();

  462                 while (line != null)

  463                 {

  464                     string trimmed = line.Trim();

  465                     int len = trimmed.Length;

  466                     if (len <= 0)

  467                     {

  468                         // Blank lines are expected and should be skipped.

  469                     }

  470                     else if (trimmed[0] == ';')

  471                     {

  472                         // This is a comment line and should be skipped.

  473                     }

  474                     else if ((trimmed[0] == '[') && (trimmed[len - 1] == ']'))

  475                     {

  476                         // Add previous section.

  477                         sections[iniSection.Name] = iniSection;

  478                         // Get the name of the section just encountered.

  479                         string name = trimmed.Substring(1, (len - 2)).Trim();

  480                         // Look for an existing section.  Append to it if found.

  481                         if (!sections.TryGetValue(name, out iniSection))

  482                         {

  483                             // Start new section.

  484                             iniSection = new IniSection();

  485                             iniSection.Name = name;

  486                         }

  487                     }

  488                     else

  489                     {

  490                         string[] split = trimmed.Split(DELIM_KEY_VALUE, 2);

  491                         if (split.Length == 2)

  492                         {

  493                             string key = split[0].Trim();

  494                             if (key.Length > 0)

  495                             {

  496                                 string val = split[1].Trim();

  497                                 iniSection.Contents[key] = val;

  498                             }

  499                         }

  500                     }

  501 

  502                     line = reader.ReadLine();

  503                 }

  504 

  505                 // Add the last section.

  506                 sections[iniSection.Name] = iniSection;

  507             }

  508 

  509             // Not caught: ArgumentOutOfRangeException: Can't happen as coded.

  510             // Not caught: IndexOutOfRangeException: Can't happen as coded.

  511 

  512         } // end ReadIniFile

  513 

  514         //**********************************************************************

  515         /// <summary>

  516         /// Retrieves a reference to the object containing the contents of the

  517         /// section identified by <see cref="CurrentSection"/>.

  518         /// </summary>

  519         /// <returns>

  520         /// The section or <see langword="null"/> if a matching section could

  521         /// not be found.

  522         /// </returns>

  523         private IniSection GetCurrentSection()

  524         {

  525             IniSection iniSection;

  526             if (!Sections.TryGetValue(CurrentSection, out iniSection))

  527             {

  528                 iniSection = null;

  529             }

  530 

  531             return (iniSection);

  532         }

  533 

  534 

  535         #region Nested classes

  536 

  537         //**********************************************************************

  538         /// <summary>

  539         /// Encapsulates an INI file section, which basically identifies a

  540         /// namespace for defining key=value pairs.

  541         /// </summary>

  542         /// <remarks>

  543         /// Instances of this class are used by the enclosing class to contain

  544         /// the key=value pairs defined within a section of an INI file.

  545         /// <para>$Id$</para>

  546         /// <author>Author: Yours truly</author>

  547         /// </remarks>

  548         //**********************************************************************

  549         protected class IniSection

  550         {

  551             /// <summary>Stores the <see cref="Name"/>.</summary>

  552             private string m_name = String.Empty;

  553             /// <summary>Holds the <see cref="Contents"/>.</summary>

  554             private readonly Dictionary<string, string> m_contents =

  555                 new Dictionary<string, string>();

  556 

  557             //******************************************************************

  558             /// <summary>

  559             /// Gets or sets the section name.

  560             /// </summary>

  561             /// <value>Defaults to <see cref="String.Empty">the empty

  562             /// string</see>.  Will never be <see langword="null"/>.</value>

  563             /// <remarks>

  564             /// This is the string in between the square brackets.  For example,

  565             /// a section identified using "[SectionName]" is named

  566             /// "SectionName".

  567             /// </remarks>

  568             /// <exception cref="ArgumentNullException">An attempt was made to

  569             /// set this property to <see langword="null"/>.</exception>

  570             public string Name

  571             {

  572                 get { return m_name; }

  573 

  574                 set

  575                 {

  576                     if (value == null) throw new ArgumentNullException("value");

  577                     m_name = value;

  578                 }

  579             }

  580 

  581             //******************************************************************

  582             /// <summary>

  583             /// Gets a dictionary of string values keyed by string names,

  584             /// identifying the key=value pairs defined within the INI file

  585             /// section represented by the current instance.

  586             /// </summary>

  587             /// <value>This property will always return the same object.  It

  588             /// will never return <see langword="null"/>.</value>

  589             public IDictionary<string, string> Contents

  590             {

  591                 get { return m_contents; }

  592             }

  593         }

  594 

  595         #endregion

  596 

  597     } // end class MyBaseIniFile

  598 

  599 } // end namespace MyIniFileReader



The derived class MyIniFile makes use of the base class to actually implement a number of required stuffs ....


    1 // Copyright © 2009 Yours truly.  All rights reserved.

    2 

    3 using System;

    4 using System.Collections.Generic;

    5 using System.Diagnostics;

    6 using System.IO;

    7 using System.Text;

    8 using System.Threading;

    9 

   10 namespace MyIniFileReader

   11 {

   12     //**************************************************************************

   13     /// <summary>

   14     /// Encapsulates a Windows INI file.

   15     /// </summary>

   16     /// <seealso cref="MyIniFile"/>

   17     /// <remarks>

   18     ///  INI files always reside in the application User subdirectory.

   19     /// They can optionally monitor themselves and report changes.

   20     /// <para>

   21     /// By default, self-monitoring is off (false).  The caller should always

   22     /// invoke <see cref="Load()"/> to ensure the first read occurs.

   23     /// <example>

   24     /// Usage example:

   25     /// this.m_IniFile = new MyIniFile("MyIniFile.ini");

   26     /// this.m_IniFile.StateChanged += this.IniFileHandler;

   27     /// this.m_IniFile.Load();

   28     /// this.m_IniFile.Monitor = true;

   29     /// </example>

   30     /// </para>

   31     /// <para>$Id$</para>

   32     /// <author>Author: Yours truly</author>

   33     /// </remarks>

   34     /// <threadsafety static="true" instance="false"/>

   35     //**************************************************************************

   36     public class MyIniFile : MyBaseIniFile, IDisposable

   37     {

   38         /// <summary>The built-in file watcher.</summary>

   39         private readonly FileSystemWatcher m_configWatcher;

   40 

   41         /// <summary>The handler for file watcher events.</summary>

   42         private readonly FileSystemEventHandler WatcherEventHandler;

   43 

   44         private readonly RenamedEventHandler RenameHandler;

   45 

   46         //**********************************************************************

   47         /// <summary>

   48         /// An event is raised whenever the instance's state changes.

   49         /// </summary>

   50         /// <remarks>

   51         /// This will happen after the actual file has been changed and new

   52         /// values have been read into this object.

   53         /// </remarks>

   54         [field: NonSerialized]

   55         public event EventHandler<MyIniEventArgs> StateChanged;

   56 

   57         //**********************************************************************

   58         /// <summary>

   59         /// Constructor.

   60         /// </summary>

   61         /// <seealso cref="Load()"/>

   62         /// <remarks>

   63         /// The default is to set <see cref="Monitor"/> to <c>false</c>.

   64         /// This means that by default, the <c>MyIniFile</c> does NOT do

   65         /// self-monitoring.  However, it also means that the caller has time to

   66         /// hook into the <see cref="StateChanged"/> event and then initiate

   67         /// self-monitoring by setting <c>Monitor = true</c>.

   68         /// <para>

   69         /// Without self-monitoring, the caller must explicitly call

   70         /// <see cref="Load()"/>.

   71         /// </para>

   72         /// </remarks>

   73         /// <param name="directoryPath">The directory path which contains the

   74         /// file with the specified <paramref name="fileName"/>.</param>

   75         /// <param name="fileName">The basename of the file.</param>

   76         public MyIniFile(string directoryPath, string fileName)

   77         {

   78             this.m_configWatcher = new FileSystemWatcher();

   79 

   80             // Sets the folder being watched

   81             this.m_configWatcher.Path = directoryPath;

   82 

   83             //  Watch for changes in LastAccess and LastWrite times, and

   84             //  the renaming of files or directories.

   85             this.m_configWatcher.NotifyFilter = NotifyFilters.LastAccess |

   86                 NotifyFilters.LastWrite | NotifyFilters.FileName |

   87                 NotifyFilters.DirectoryName;

   88 

   89             // Sets the configuration file to be watched.

   90             this.m_configWatcher.Filter = fileName;

   91             // Register a handler that gets called if the

   92             //  FileSystemWatcher needs to report an error.

   93             this.m_configWatcher.Error += new ErrorEventHandler(HandleError);

   94 

   95             // Add event handler.

   96             this.WatcherEventHandler =

   97                 new FileSystemEventHandler(this.HandleWatcherEvent);

   98             this.RenameHandler =

   99                 new RenamedEventHandler(this.HandleWatcherEvent);

  100 

  101         } // end MyIniFile

  102 

  103         //**********************************************************************

  104         /// <summary>

  105         /// The INI file basename.

  106         /// </summary>

  107         /// <seealso cref="Path"/>

  108         /// <seealso cref="FullPathName"/>

  109         public string Name

  110         {

  111             get { return this.m_configWatcher.Filter; }

  112         }

  113 

  114         //**********************************************************************

  115         /// <summary>

  116         /// The path to the folder containing the INI file.

  117         /// </summary>

  118         /// <seealso cref="Name"/>

  119         /// <seealso cref="FullPathName"/>

  120         public string Path

  121         {

  122             get { return this.m_configWatcher.Path; }

  123         }

  124 

  125         //**********************************************************************

  126         /// <summary>

  127         /// The full pathname of the INI file.

  128         /// </summary>

  129         /// <seealso cref="Name"/>

  130         /// <seealso cref="Path"/>

  131         public string FullPathName

  132         {

  133             get { return this.Path + @"\" + this.Name; }

  134         }

  135 

  136         //**********************************************************************

  137         /// <summary>

  138         /// Gets or sets this object's ability to monitor the INI file for changes.

  139         /// </summary>

  140         /// <remarks>

  141         /// If <c>true</c> the <see cref="StateChanged"/> event will be raised

  142         /// by this object whenever there is a change to the INI file.

  143         /// </remarks>

  144         /// <value>

  145         /// If <c>true</c> the file at <see cref="FullPathName"/> will be monitored.

  146         /// </value>

  147         public bool Monitor

  148         {

  149             get { return this.m_configWatcher.EnableRaisingEvents; }

  150             set

  151             {

  152                 if (value)

  153                 {

  154                     this.m_configWatcher.Changed += this.WatcherEventHandler;

  155                     this.m_configWatcher.Created += this.WatcherEventHandler;

  156                     this.m_configWatcher.Deleted += this.WatcherEventHandler;

  157                     this.m_configWatcher.Renamed += this.RenameHandler;

  158                 }

  159                 else

  160                 {

  161                     this.m_configWatcher.Changed -= this.WatcherEventHandler;

  162                     this.m_configWatcher.Created -= this.WatcherEventHandler;

  163                     this.m_configWatcher.Deleted -= this.WatcherEventHandler;

  164                     this.m_configWatcher.Renamed -= this.RenameHandler;

  165                 }

  166                 this.m_configWatcher.EnableRaisingEvents = value;

  167             }

  168         }

  169 

  170         //**********************************************************************

  171         /// <summary>

  172         /// Reads the information from the INI file at

  173         /// <see cref="FullPathName"/> into this instance.

  174         /// </summary>

  175         /// <remarks>

  176         /// This is the preferred function overload for callers to use.

  177         /// </remarks>

  178         /// <returns>

  179         /// <see langword="true"/> if the indicated file was loaded;

  180         /// <see langword="false"/> if the indicated file cannot be loaded.

  181         /// </returns>

  182         /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is

  183         /// <see langword="null"/> or empty.</exception>

  184         /// <exception cref="ArgumentException"><paramref name="fileName"/> is an

  185         /// invalid string.</exception>

  186         /// <exception cref="IOException"><paramref name="fileName"/> includes an

  187         /// incorrect or invalid syntax for file name, directory name, or volume

  188         /// label, or possibly, if the file is opened for editing.

  189         /// </exception>

  190         public bool Load()

  191         {

  192             return this.Load(this.Name);

  193         } // end Load()

  194 

  195         //**********************************************************************

  196         /// <summary>

  197         /// Reads the information from the indicated INI file into this instance.

  198         /// </summary>

  199         /// <seealso cref="Load()"/>

  200         /// <remarks>

  201         /// The <paramref name="fileName"/> must match the <c>fileName</c> parameter

  202         /// originally passed to the constructor of this object, (which is also

  203         /// the same value that <see cref="Name"/> returns).

  204         /// </remarks>

  205         /// <param name="fileName">The basename of the INI file.</param>

  206         /// <returns>

  207         /// <see langword="true"/> if the indicated file was loaded;

  208         /// <see langword="false"/> if the indicated file cannot be loaded.

  209         /// </returns>

  210         /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is

  211         /// <see langword="null"/> or empty.</exception>

  212         /// <exception cref="ArgumentException"><paramref name="fileName"/> is an

  213         /// invalid string.</exception>

  214         /// <exception cref="IOException"><paramref name="fileName"/> includes an

  215         /// incorrect or invalid syntax for file name, directory name, or volume

  216         /// label, or possibly, if the file is opened for editing.

  217         /// </exception>

  218         public override bool Load(string fileName)

  219         {

  220             if (String.IsNullOrEmpty(fileName))

  221             {

  222                 throw new ArgumentNullException("Invalid INI path.");

  223             }

  224             if (!fileName.Equals(this.Name))

  225             {

  226                 // Though, we do change the name of the file as it is renamed.

  227                 throw

  228                     new ArgumentException("Load and Constructor params must match!");

  229             }

  230 

  231             IDictionary<string, IniSection> saveSections = null;

  232             if (this.Monitor)

  233             {

  234                 IDictionary<string, IniSection> tmpSections = base.Sections;

  235                 if (null != tmpSections && 0 < tmpSections.Count)

  236                 {

  237                     saveSections =

  238                         MyIniFile.CopyIniSections(tmpSections);

  239                 }

  240             }

  241 

  242             IOException excp = null;

  243             bool flag = false;

  244             int tries = 0;

  245             while (!flag && 3 > tries)

  246             {

  247                 try

  248                 {

  249                     flag = base.Load(this.FullPathName);

  250                     if (!flag)

  251                     {

  252                         tries = 3; // no need to retry.

  253                     }

  254                 }

  255                 // DEVNOTE: If the user has the file open for editing, we may

  256                 // get an IOException after the save.  But if the file is then

  257                 // closed or made available soon thereafter, we might succeed

  258                 // if we have another go at reading it.

  259                 catch (IOException e)

  260                 {

  261                     excp = e;

  262                     Thread.Sleep(2000);

  263                 }

  264                 tries++;

  265             }

  266             if (!flag)

  267             {

  268                 if (null != excp)

  269                 {

  270                     // Logging??

  271                 }

  272                 else

  273                 {

  274                     // Logging??

  275                 }

  276             }

  277 

  278 

  279             if (flag && null != saveSections)

  280             {

  281                 IDictionary<string, IDictionary<string, object>> oldSections =

  282                     MyIniFile.ConvertIniSections(saveSections);

  283                 IDictionary<string, IDictionary<string, object>> newSections =

  284                     MyIniFile.ConvertIniSections(this.Sections);

  285                 MyIniEventArgs ea =

  286                     new MyIniEventArgs(oldSections, newSections);

  287                 // DEVNOTE: During debugging, it was observed that the File

  288                 // Watcher could raise apparently redundant events.

  289                 if (0 < ea.Changes.Count ||

  290                     0 < ea.Deletions.Count ||

  291                     0 < ea.Additions.Count)

  292                 {

  293                     this.OnStateChanged(ea);

  294                 }

  295             }

  296 

  297             return flag;

  298         } // end Load

  299 

  300         //**********************************************************************

  301         /// <summary>

  302         /// Get a dictionary of name-value pairs in the given

  303         /// <paramref name="sectionName"/>.

  304         /// </summary>

  305         /// <param name="sectionName">The name of the INI section.</param>

  306         /// <returns>An <see cref="IDictionary{TKey, TValue}"/> of name-value

  307         /// pairs.</returns>

  308         public IDictionary<string, object> GetSection(string sectionName)

  309         {

  310             IDictionary<string, object> section = null;

  311 

  312             IniSection iniSection = null;

  313             bool flag = this.Sections.TryGetValue(sectionName, out iniSection);

  314             if (flag && null != iniSection)

  315             {

  316                 section = ConvertOneIniSection(iniSection);

  317             }

  318 

  319             return section;

  320         } // end GetSection

  321 

  322         //**********************************************************************

  323         /// <summary>

  324         /// Releases all resources used by the current instance.

  325         /// </summary>

  326         public void Dispose()

  327         {

  328             this.Dispose(true);

  329             GC.SuppressFinalize(this);

  330         } // end Dispose

  331 

  332         //**********************************************************************

  333         /// <summary>

  334         /// Releases the unmanaged resources used by this Component and

  335         /// optionally releases the managed resources.

  336         /// </summary>

  337         /// <param name="isDisposing"><see langword="true"/> to release both

  338         /// managed and unmanaged resources; <see langword="false"/> to release

  339         /// only unmanaged resources.</param>

  340         protected virtual void Dispose(bool isDisposing)

  341         {

  342             if (isDisposing)

  343             {

  344                 this.Monitor = false;

  345                 this.m_configWatcher.Dispose();

  346             }

  347         } // end Dispose

  348 

  349         //**********************************************************************

  350         /// <summary>

  351         /// Create a shallow copy of <paramref name="source"/>.

  352         /// </summary>

  353         /// <param name="source"><c>IDictionary{string, IniSection}</c></param>

  354         /// <returns><c>IDictionary{string, IniSection}</c></returns>

  355         static protected IDictionary<string, IniSection> CopyIniSections

  356         (

  357             IDictionary<string, IniSection> source

  358         )

  359         {

  360             IDictionary<string, IniSection> copy =

  361                 new Dictionary<string, IniSection>(source);

  362 

  363             return copy;

  364         } // end CopySections

  365 

  366         //**********************************************************************

  367         /// <summary>

  368         /// Convert an <c>IDictionary{string, IniSection}</c> to an

  369         /// <c>IDictionary{string, IDictionary{string, object}</c>.

  370         /// </summary>

  371         /// <param name="source"><c>IDictionary{string, IniSection}</c></param>

  372         /// <returns><c>IDictionary{string, IDictionary{string, object}</c></returns>

  373         static protected IDictionary<string, IDictionary<string, object>> ConvertIniSections

  374         (

  375             IDictionary<string, IniSection> source

  376         )

  377         {

  378             IDictionary<string, IDictionary<string, object>> conversion =

  379                 new Dictionary<string, IDictionary<string, object>>(source.Count);

  380 

  381             foreach (IniSection iniSection in source.Values)

  382             {

  383                 IDictionary<string, object> section =

  384                     ConvertOneIniSection(iniSection);

  385                 conversion.Add(iniSection.Name, section);

  386             }

  387 

  388             return conversion;

  389         } // end ConvertIniSections

  390 

  391         //**********************************************************************

  392         /// <summary>

  393         /// Convert the given <see cref="MyBaseIniFile.IniSection"/> to an ordinary

  394         /// generic <see cref="IDictionary{TKey, TValue}"/>.

  395         /// </summary>

  396         /// <param name="iniSection">An internal class type representing an

  397         /// INI file section.</param>

  398         /// <returns>An <see cref="IDictionary{TKey, TValue}"/> of name-value

  399         /// pairs.</returns>

  400         static protected IDictionary<string, object> ConvertOneIniSection

  401         (

  402             IniSection iniSection

  403         )

  404         {

  405             Debug.Assert(null != iniSection, "Invalid IniSection!");

  406 

  407             IDictionary<string, object> section =

  408                 new Dictionary<string, object>(iniSection.Contents.Count);

  409             foreach (KeyValuePair<string, string> keyValue in iniSection.Contents)

  410             {

  411                 section.Add(keyValue.Key, keyValue.Value);

  412             }

  413             return section;

  414         } // end ConvertOneIniSection

  415 

  416         //**********************************************************************

  417         /// <summary>

  418         /// Raise the <see cref="StateChanged"/> event.

  419         /// </summary>

  420         /// <param name="ea">The <c>EventArgs</c> for this event.</param>

  421         protected void OnStateChanged(MyIniEventArgs ea)

  422         {

  423             EventHandler<MyIniEventArgs> tmp = this.StateChanged;

  424             if (null != tmp)

  425             {

  426                 tmp(this, ea);

  427             }

  428         } // end OnStateChanged

  429 

  430         ///********************************************************************

  431         /// <summary>

  432         /// Handle the change event from the file watcher.

  433         /// </summary>

  434         /// <param name="source">The <see cref="FileSystemWatcher"/> that raised

  435         /// the event.</param>

  436         /// <param name="e">Contains the event data.</param>

  437         private void HandleWatcherEvent

  438         (

  439             object source,

  440             FileSystemEventArgs e

  441         )

  442         {

  443             WatcherChangeTypes wct = e.ChangeType;

  444             switch (wct)

  445             {

  446                 case WatcherChangeTypes.Changed:

  447                     this.Load(e.Name);

  448                     Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());

  449                     break;

  450                 // TODO: These other events are not getting picked up for

  451                 // reasons unknown.

  452                 case WatcherChangeTypes.Created:

  453                     this.Load(e.Name);

  454                     Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());

  455                     break;

  456                 case WatcherChangeTypes.Deleted:

  457                     Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());

  458                     break;

  459                 case WatcherChangeTypes.Renamed:

  460                     // If you want, you can save the renamed filename/path in

  461                     // some application config file so next time you know which

  462                     // file to pick. That way one can get rid of the

  463                     // parameterized constructor.

  464                     this.m_configWatcher.Filter = e.Name;

  465                     this.Load(e.Name);

  466                     Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());

  467                     break;

  468                 default:

  469                     Debug.Fail("ChangeType?");

  470                     break;

  471             }

  472         } //end HandleWatcherEvent

  473 

  474         // *********************************************************************

  475         /// <summary>

  476         /// This method is called when the FileSystemWatcher detects an error.

  477         /// </summary>

  478         /// <param name="source">The <see cref="FileSystemWatcher"/> that raised

  479         /// the event.</param>

  480         /// <param name="e">Contains the error event data.</param>

  481         private void HandleError(object source, ErrorEventArgs e)

  482         {

  483             //  Show that an error has been detected.

  484             Console.WriteLine("The FileSystemWatcher has detected an error");

  485             //  Give more information if the error is due to an internal buffer overflow.

  486             if (e.GetException().GetType() == typeof(InternalBufferOverflowException))

  487             {

  488                 //  This can happen if Windows is reporting many file system events quickly

  489                 //  and internal buffer of the  FileSystemWatcher is not large enough to handle this

  490                 //  rate of events. The InternalBufferOverflowException error informs the application

  491                 //  that some of the file system events are being lost.

  492                 Console.WriteLine(("The file system watcher experienced an internal buffer overflow: " +

  493                     e.GetException().Message));

  494             }

  495         }

  496     } // end class MyIniFile

  497 } // end namespace MyIniFileReader




I have made use of the EventArgs class to have our own custom event arguments to listen to various changes in ini file...This class is MyIniEventArgs.


    1 // Copyright © 2009 Yours truly.  All rights reserved.

    2 

    3 using System;

    4 using System.Collections.Generic;

    5 using System.Diagnostics;

    6 

    7 

    8 namespace MyIniFileReader

    9 {

   10     //**************************************************************************

   11     /// <summary>

   12     /// Event data for <see cref="MyIniFile.StateChanged"/> event.

   13     /// </summary>

   14     /// <remarks>

   15     /// <see cref="MyIniFile"/>s monitor themselves and raise the

   16     /// <c>StateChanged</c> with this <c>MyComIniEventArgs</c> object.

   17     /// <para>

   18     /// This <c>EventArgs</c> object contains the information as to what

   19     /// changed as well as the new and old values.

   20     /// </para>

   21     /// <para>$Id$</para>

   22     /// <author>Author: Yours truly</author>

   23     /// </remarks>

   24     /// <threadsafety static="true" instance="false"/>

   25     //**************************************************************************

   26     public class MyIniEventArgs : EventArgs

   27     {

   28         private readonly IDictionary<string, IDictionary<string, object>> m_additions;

   29         private readonly IDictionary<string, IDictionary<string, object>> m_deletions;

   30         private readonly IDictionary<string, IDictionary<string, MyTuple<object, object>>> m_changes;

   31 

   32         //**********************************************************************

   33         /// <summary>

   34         /// Initializes an instance of <see cref="MyIniEventArgs"/>.

   35         /// </summary>

   36         /// <param name="oldSections">The collection of old key-value pairs.</param>

   37         /// <param name="newSections">The collection of new key-value pairs.</param>

   38         public MyIniEventArgs

   39         (

   40             IDictionary<string, IDictionary<string, object>> oldSections,

   41             IDictionary<string, IDictionary<string, object>> newSections

   42         )

   43         {

   44             this.m_additions =

   45                 new Dictionary<string, IDictionary<string, object>>();

   46             this.m_deletions =

   47                 new Dictionary<string, IDictionary<string, object>>();

   48             this.m_changes =

   49                 new Dictionary<string, IDictionary<string, MyTuple<object, object>>>();

   50 

   51             // Iterate over the oldSections, checking if they are present in newSections.

   52             // If not present, copy a missing oldSection in its entirety to Deletions.

   53             foreach (KeyValuePair<string, IDictionary<string, object>> section in oldSections)

   54             {

   55                 if (!newSections.ContainsKey(section.Key))

   56                 {

   57                     this.m_deletions.Add(section);

   58                 }

   59             }

   60 

   61             // Iterate over the newSections, checking if they are present in oldSections.

   62             // If not present, copy the missing newSection in its entirety to Additions.

   63             foreach (KeyValuePair<string, IDictionary<string, object>> section in newSections)

   64             {

   65                 if (!oldSections.ContainsKey(section.Key))

   66                 {

   67                     this.m_additions.Add(section);

   68                 }

   69             }

   70 

   71             // Iterate over newSections that were present in oldSections, and evaluate

   72             // the KeyValuePairs for additions, deletions or changes.

   73             foreach (KeyValuePair<string, IDictionary<string, object>> section in newSections)

   74             {

   75                 if (oldSections.ContainsKey(section.Key))

   76                 {

   77                     this.EvaluateSection(section, oldSections[section.Key]);

   78                 }

   79             }

   80         } // end ctor

   81 

   82         //**********************************************************************

   83         /// <summary>

   84         /// Gets a dictionary of all sections that have been newly added, each

   85         /// with their own dictionary of the newly-added key-value pairs.

   86         /// </summary>

   87         public IDictionary<string, IDictionary<string, object>> Additions

   88         {

   89             get { return this.m_additions; }

   90         }

   91 

   92         //**********************************************************************

   93         /// <summary>

   94         /// The entries appearing in here were formerly existing entries that

   95         /// are no longer found.

   96         /// </summary>

   97         public IDictionary<string, IDictionary<string, object>> Deletions

   98         {

   99             get { return this.m_deletions; }

  100         }

  101 

  102         //**********************************************************************

  103         /// <summary>

  104         /// The entries appearing in here were all existing entries that got

  105         /// modified.

  106         /// </summary>

  107         public IDictionary<string, IDictionary<string, MyTuple<object, object>>> Changes

  108         {

  109             get { return this.m_changes; }

  110         }

  111 

  112         //**********************************************************************

  113         /// <summary>

  114         /// Determine which entries changed, which were deleted and which were

  115         /// added.

  116         /// </summary>

  117         /// <param name="section">The INI file section.</param>

  118         /// <param name="oldValues">A dictionary of old values.</param>

  119         private void EvaluateSection

  120         (

  121             KeyValuePair<string, IDictionary<string, object>> section,

  122             IDictionary<string, object> oldValues

  123         )

  124         {

  125             foreach (KeyValuePair<string, object> oldValue in oldValues)

  126             {

  127                 if (!section.Value.ContainsKey(oldValue.Key))

  128                 {

  129                     this.AddToDeletions(section.Key, oldValue);

  130                 }

  131             }

  132 

  133             foreach (KeyValuePair<string, object> value in section.Value)

  134             {

  135                 if (oldValues.ContainsKey(value.Key))

  136                 {

  137                     object obj = oldValues[value.Key];

  138                     if (!obj.Equals(value.Value))

  139                     {

  140                         MyTuple<object, object> oldAndNew =

  141                             new MyTuple<object, object>(obj, value.Value);

  142                         this.AddToChanges(section.Key, value.Key, oldAndNew);

  143                     }

  144                 }

  145                 else

  146                 {

  147                     this.AddToAdditions(section.Key, value);

  148                 }

  149             }

  150         } // end EvaluateSection

  151 

  152         // *********************************************************************

  153         /// <summary>

  154         /// Adds the changed values to the list of values.

  155         /// </summary>

  156         /// <param name="sectionName">Name of section containing the changed

  157         /// values.</param>

  158         /// <param name="entryName">The name of key(entry) for which the value

  159         /// is changed.</param>

  160         /// <param name="value">The changed value.</param>

  161         private void AddToChanges

  162         (

  163             string sectionName,

  164             string entryName,

  165             MyTuple<object, object> value

  166         )

  167         {

  168             if (!Changes.ContainsKey(sectionName))

  169             {

  170                 IDictionary<string, MyTuple<object, object>> tmpSection =

  171                     new Dictionary<string, MyTuple<object, object>>();

  172                 this.m_changes.Add(sectionName, tmpSection);

  173             }

  174 

  175             IDictionary<string, MyTuple<object, object>> section =

  176                 Changes[sectionName];

  177 

  178             Debug.Assert(!section.ContainsKey(entryName),

  179                 "ERROR: Change for this value has already been posted!");

  180 

  181             section.Add(entryName, value);

  182         } // end AddToChanges

  183 

  184         //**********************************************************************

  185         /// <summary>

  186         /// Adds a list of additions for the new values.

  187         /// </summary>

  188         /// <param name="sectionName">The name of section for which additions

  189         /// are required.</param>

  190         /// <param name="keyValuePair">The key value pair to be added.</param>

  191         private void AddToAdditions

  192         (

  193             string sectionName,

  194             KeyValuePair<string, object> keyValuePair

  195         )

  196         {

  197             if (!Additions.ContainsKey(sectionName))

  198             {

  199                 IDictionary<string, object> tmpSection =

  200                     new Dictionary<string, object>();

  201                 this.m_additions.Add(sectionName, tmpSection);

  202             }

  203 

  204             IDictionary<string, object> section =

  205                 Additions[sectionName];

  206 

  207             Debug.Assert(!section.ContainsKey(keyValuePair.Key),

  208                 "ERROR: Addition for this value has already been posted!");

  209 

  210             section.Add(keyValuePair);

  211         } // end AddToAdditions

  212 

  213         //**********************************************************************

  214         /// <summary>

  215         /// Adds the <paramref name="keyValuePair"/> for the specified

  216         /// <see cref="sectionName"/> to the list of values to be deleted.

  217         /// </summary>

  218         /// <param name="sectionName">The name of section for which additions

  219         /// are required.</param>

  220         /// <param name="keyValuePair">The key value pair to be added.</param>

  221         private void AddToDeletions

  222         (

  223             string sectionName,

  224             KeyValuePair<string, object> keyValuePair

  225         )

  226         {

  227             if (!Deletions.ContainsKey(sectionName))

  228             {

  229                 IDictionary<string, object> tmpSection =

  230                     new Dictionary<string, object>();

  231                 this.m_deletions.Add(sectionName, tmpSection);

  232             }

  233 

  234             IDictionary<string, object> section =

  235                 Deletions[sectionName];

  236 

  237             Debug.Assert(!section.ContainsKey(keyValuePair.Key),

  238                 "ERROR: Deletion for this value has already been posted!");

  239 

  240             section.Add(keyValuePair);

  241         } // end AddToDeletions

  242 

  243     } // end class MyIniEventArgs

  244 } // end namespace MyIniFileReader



Throughout the code I have used MyTuple class from an earlier post.

I hope this helps someone ...

2 comments:

  1. Did you have a download link please?

    ReplyDelete
  2. Kindly send me the demo app with above implementation.

    Thanking you in anticipation

    Aamir Ahmed

    ReplyDelete

Please leave your opinion...