Click here to Skip to main content
15,888,733 members
Articles / Programming Languages / C#
Tip/Trick

A Simple and Efficient INI File Reader in C#

Rate me:
Please Sign up or sign in to vote.
4.88/5 (11 votes)
10 May 2014CPOL 80.3K   22   17
A class for reading values by section and key from a standard ".ini" initialization file.

Introduction

I prefer XML files for persisting data that will be written by machine. However, good old fashioned ".ini" files are still the format that is simplest for human users to open and edit in Notepad.

Here is a class in C# that makes it really easy to read these initialization files in your program. Values are loaded into a Dictionary for fast access. Each reader method includes a default value in case the key does not exist. Multiple values may share the same section and key, and can be accessed as a string array.

C#
using System;
using System.Collections.Generic;
using System.IO;

namespace IniFile
{
    /// <summary>
    /// A class for reading values by section and key from a standard ".ini" initialization file.
    /// </summary>
    /// <remarks>
    /// Section and key names are not case-sensitive. Values are loaded into a hash table for fast access.
    /// Use <see cref="GetAllValues"/> to read multiple values that share the same section and key.
    /// Sections in the initialization file must have the following form:
    /// <code>
    ///     ; comment line
    ///     [section]
    ///     key=value
    /// </code>
    /// </remarks>
    public class IniFile
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="IniFile"/> class.
        /// </summary>
        /// <param name="file">The initialization file path.</param>
        /// <param name="commentDelimiter">The comment delimiter string (default value is ";").
        /// </param>
        public IniFile(string file, string commentDelimiter = ";")
        {
            CommentDelimiter = commentDelimiter;
            TheFile = file;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="IniFile"/> class.
        /// </summary>
        public IniFile()
        {
            CommentDelimiter = ";";
        }

        /// <summary>
        /// The comment delimiter string (default value is ";").
        /// </summary>
        public string CommentDelimiter { get; set; }

        private string theFile = null;

        /// <summary>
        /// The initialization file path.
        /// </summary>
        public string TheFile
        {
            get
            {
                return theFile;
            }
            set
            {
                theFile = null;
                dictionary.Clear();
                if (File.Exists(value))
                {
                    theFile = value;
                    using (StreamReader sr = new StreamReader(theFile))
                    {
                        string line, section = "";
                        while ((line = sr.ReadLine()) != null)
                        {
                            line = line.Trim();
                            if (line.Length == 0) continue;  // empty line
                            if (!String.IsNullOrEmpty(CommentDelimiter) && line.StartsWith(CommentDelimiter))
                                continue;  // comment

                            if (line.StartsWith("[") && line.Contains("]"))  // [section]
                            {
                                int index = line.IndexOf(']');
                                section = line.Substring(1, index - 1).Trim();
                                continue;
                            }

                            if (line.Contains("="))  // key=value
                            {
                                int index = line.IndexOf('=');
                                string key = line.Substring(0, index).Trim();
                                string val = line.Substring(index + 1).Trim();
                                string key2 = String.Format("[{0}]{1}", section, key).ToLower();

                                if (val.StartsWith("\"") && val.EndsWith("\""))  // strip quotes
                                    val = val.Substring(1, val.Length - 2);

                                if (dictionary.ContainsKey(key2))  // multiple values can share the same key
                                {
                                    index = 1;
                                    string key3;
                                    while (true)
                                    {
                                        key3 = String.Format("{0}~{1}", key2, ++index);
                                        if (!dictionary.ContainsKey(key3))
                                        {
                                            dictionary.Add(key3, val);
                                            break;
                                        }
                                    }
                                }
                                else
                                {
                                    dictionary.Add(key2, val);
                                }
                            }
                        }
                    }
                }
            }
        }

        // "[section]key"   -> "value1"
        // "[section]key~2" -> "value2"
        // "[section]key~3" -> "value3"
        private Dictionary<string, string> dictionary = new Dictionary<string, string>();

        private bool TryGetValue(string section, string key, out string value)
        {
            string key2;
            if (section.StartsWith("["))
                key2 = String.Format("{0}{1}", section, key);
            else
                key2 = String.Format("[{0}]{1}", section, key);

            return dictionary.TryGetValue(key2.ToLower(), out value);
        }

        /// <summary>
        /// Gets a string value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>The value.</returns>
        /// <seealso cref="GetAllValues"/>
        public string GetValue(string section, string key, string defaultValue = "")
        {
            string value;
            if (!TryGetValue(section, key, out value))
                return defaultValue;

            return value;
        }

        /// <summary>
        /// Gets a string value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns>The value.</returns>
        /// <seealso cref="GetValue"/>
        public string this[string section, string key]
        {
            get
            {
                return GetValue(section, key);
            }
        }

        /// <summary>
        /// Gets an integer value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="minValue">Optional minimum value to be enforced.</param>
        /// <param name="maxValue">Optional maximum value to be enforced.</param>
        /// <returns>The value.</returns>
        public int GetInteger(string section, string key, int defaultValue = 0, 
            int minValue = int.MinValue, int maxValue = int.MaxValue)
        {
            string stringValue;
            if (!TryGetValue(section, key, out stringValue))
                return defaultValue;

            int value;
            if (!int.TryParse(stringValue, out value))
            {
                double dvalue;
                if (!double.TryParse(stringValue, out dvalue))
                    return defaultValue;
                value = (int)dvalue;
            }

            if (value < minValue)
                value = minValue;
            if (value > maxValue)
                value = maxValue;
            return value;
        }

        /// <summary>
        /// Gets a double floating-point value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="minValue">Optional minimum value to be enforced.</param>
        /// <param name="maxValue">Optional maximum value to be enforced.</param>
        /// <returns>The value.</returns>
        public double GetDouble(string section, string key, double defaultValue = 0, 
            double minValue = double.MinValue, double maxValue = double.MaxValue)
        {
            string stringValue;
            if (!TryGetValue(section, key, out stringValue))
                return defaultValue;

            double value;
            if (!double.TryParse(stringValue, out value))
                return defaultValue;

            if (value < minValue)
                value = minValue;
            if (value > maxValue)
                value = maxValue;
            return value;
        }

        /// <summary>
        /// Gets a boolean value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>The value.</returns>
        public bool GetBoolean(string section, string key, bool defaultValue = false)
        {
            string stringValue;
            if (!TryGetValue(section, key, out stringValue))
                return defaultValue;

            return (stringValue != "0" && !stringValue.StartsWith("f", true, null));
        }

        /// <summary>
        /// Gets an array of string values by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns>The array of values, or null if none found.</returns>
        /// <seealso cref="GetValue"/>
        public string[] GetAllValues(string section, string key)
        {
            string key2, key3, value;
            if (section.StartsWith("["))
                key2 = String.Format("{0}{1}", section, key).ToLower();
            else
                key2 = String.Format("[{0}]{1}", section, key).ToLower();

            if (!dictionary.TryGetValue(key2, out value))
                return null;

            List<string> values = new List<string>();
            values.Add(value);
            int index = 1;
            while (true)
            {
                key3 = String.Format("{0}~{1}", key2, ++index);
                if (!dictionary.TryGetValue(key3, out value))
                    break;
                values.Add(value);
            }

            return values.ToArray();
        }
    }
}

Using the class in your program is as simple as instantiating it and calling the various reader methods.

C#
var iniFile = new IniFile("MyFile.ini"); 
string font = iniFile.GetValue("Text Style", "Font", "Arial");
int  size = iniFile.GetInteger("Text Style", "Size", 12);
bool bold = iniFile.GetBoolean("Text Style", "Bold", false); 

I hope that you find this code useful!

-Bruce

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Greene & Morehead Engineering, Inc.
United States United States
Motion Commander Foundation (MCF) is a set of .NET libraries, documentation and examples that enable the rapid creation of sophisticated and professional C# or Visual Basic machine control applications.

MCF provides the infrastructure (data management, plotting, alarms, message logging, user login, internationalization, Modbus, MTConnect, etc) - so that you can focus on the business logic and user interface for your machine!

MCF is designed around Microsoft .NET best practices to be intuitive for experienced developers, and examples are provided that will enable even novice .NET developers to get started easily.

Comments and Discussions

 
QuestionThank you. Works perfectly Pin
Member 1380632310-Sep-22 8:48
Member 1380632310-Sep-22 8:48 
GeneralAmazing Pin
ApocalyptoSolider11-Feb-22 0:37
ApocalyptoSolider11-Feb-22 0:37 
QuestionMulti Line reading Pin
suresh.dharmadurai17-Apr-21 16:59
suresh.dharmadurai17-Apr-21 16:59 
SuggestionHere a Read/Write Solution for your code. Very,very easy to implement in your code. Pin
Member 1373966921-Mar-18 12:38
Member 1373966921-Mar-18 12:38 
QuestionHandle errors opening ini files Pin
cirrusio17-May-17 5:59
cirrusio17-May-17 5:59 
QuestionRead all key,value pairs under same section? Pin
Member 1274164915-Sep-16 3:22
Member 1274164915-Sep-16 3:22 
AnswerRe: Read all key,value pairs under same section? Pin
Bruce Greene15-Sep-16 6:54
Bruce Greene15-Sep-16 6:54 
QuestionCannot read a key Pin
rfresh27-Jun-14 8:18
rfresh27-Jun-14 8:18 
AnswerRe: Cannot read a key Pin
BillySpees19-Dec-18 1:49
BillySpees19-Dec-18 1:49 
QuestionNice! Pin
Volynsky Alex11-May-14 14:55
professionalVolynsky Alex11-May-14 14:55 
AnswerRe: Nice! Pin
Bruce Greene11-May-14 16:38
Bruce Greene11-May-14 16:38 
GeneralRe: Nice! Pin
Volynsky Alex11-May-14 21:41
professionalVolynsky Alex11-May-14 21:41 
QuestionInteresting but ... Pin
Richard MacCutchan11-May-14 5:14
mveRichard MacCutchan11-May-14 5:14 
AnswerRe: Interesting but ... Pin
Bruce Greene11-May-14 6:21
Bruce Greene11-May-14 6:21 
Question:) Pin
Ivandro Ismael11-May-14 3:19
Ivandro Ismael11-May-14 3:19 
AnswerRe: :) Pin
Bruce Greene11-May-14 5:01
Bruce Greene11-May-14 5:01 
You're welcome! After years of getting great stuff from CP I decided to finally take the time to give some back Smile | :)
GeneralRe: :) Pin
Ivandro Ismael13-May-14 16:02
Ivandro Ismael13-May-14 16:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.