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... 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 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 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
Here is the base class to represent the ini file...
The derived class MyIniFile makes use of the base class to actually implement a number of required stuffs ....
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.
Throughout the code I have used MyTuple class from an earlier post.
I hope this helps someone ...
Sunday, April 19, 2009
Reading from ini file and subscribing to Add/Edit/Delete events
Stumble
del.icio.us
Reddit
MyWeb!
Facebook
Google bookmark
Subscribe to:
Posts (Atom)