Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

IniReader: A Simple, Tiny INI Reader

5.00/5 (17 votes)
13 Dec 2021MIT2 min read 12.8K   348  
An INI reader that is easy to use, easy to port, relatively efficient, and to the point
This is a simple single file INI reader drop in for C# projects.

Introduction

Occasionally, I need to read INI files, usually as one small part of a much larger project. As such, I really don't want it to require a bunch of infrastructure or other "buy in". I just want something simple that will get me what I need without much fuss. There is a lot of INI reader code out there, but a lot of it is gold plated, and so it wasn't what I needed. This is something simple and to the point you can just copy and paste into your project.

Using the Code

INI files are often laid out in sections, and under each section, there are key value pairs. This INI reader will combine multiple sections with the same name, and multiple keys under the same section as a single key with multiple values. It also allows you to specify values for a key one for each line. The default section is the empty string.

There are two methods of interest. The first one is for reading an INI file:

C#
var ini = Ini.Read(myTextReader);

The caller is responsible for closing the reader. The ini variable now contains a type of IDictionary<string, IDictionary<string, object>>. That's kind of a hairy type but using it is pretty simple.

Each [section] in the INI file is a key in the outer dictionary. Each key = value pair becomes a dictionary entry under that section entry's inner dictionary. If there is only one value for a key, the dictionary value will be a string. If there is more than one value per key, that key's value will be an IList<string>.

As far as the other method, you can dump the results to a string using Ini.ToString(ini).

If you want to just paste it from here, here is the relevant code in its entirety:

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

static class Ini {
    static void AddIniEntryValue(IDictionary<string, object> d, string line, 
                                 string name, int i, object o) {
        if (o is string) {
            var s = (string)o;
            d.Remove(name);
            var col = new List<string>();
            d.Add(name, col);
            col.Add(s);
            col.Add(line.Substring(i + 1).TrimStart());
        } else {
            ((List<string>)o).Add(line.Substring(i + 1).TrimStart());
        }
    }
    /// <summary>
    /// Reads an INI file as a nested dictionary. 
    /// The outer dictionary contains a dictionary for each section. 
    /// The inner dictionary contains a name, and a string or a list of strings 
    /// when an entry has multiple items.
    /// </summary>
    /// <param name="reader">The text reader</param>
    /// <param name="comparer">The comparer to use for keys. 
    /// Defaults to culture invariant and case insensitive.</param>
    /// <returns>A nested dictionary</returns>
    public static IDictionary<string, IDictionary<string, object>> 
           Read(TextReader reader, IEqualityComparer<string> comparer = null) {
        if (comparer == null) {
            comparer = StringComparer.InvariantCultureIgnoreCase;
        }
        int lc = 1;
        var result = new Dictionary<string, IDictionary<string, object>>(comparer);
        string section = "";
        string name = null;
        string line;
        while (null != (line = reader.ReadLine())) {
            var i = line.IndexOf(';');
            if (i > -1) {
                line = line.Substring(0, i);
            }
            line = line.Trim();
            if (!string.IsNullOrEmpty(line)) {
                IDictionary<string, object> d;
                if (line.Length > 2 && line[0] == '[' && line[line.Length - 1] == ']') {
                    section = line.Substring(1, line.Length - 2);
                    if (!result.TryGetValue(section, out d)) {
                        d = new Dictionary<string, object>(comparer);
                        result.Add(section, d);
                    }
                } else {
                    d = result[section];
                    i = line.IndexOf('=');
                    if (i > -1) {
                        name = line.Substring(0, i).TrimEnd();
                        object o;
                        if (d.TryGetValue(name, out o)) {
                            AddIniEntryValue(d, line, name, i, o);
                        } else
                            d.Add(name, line.Substring(i + 1).TrimStart());
                    } else if (null == name) {
                        throw new IOException("Invalid INI file at line " + lc.ToString());
                    } else {
                        var o = d[name];
                        AddIniEntryValue(d, line, name, i, o);
                    }
                }
            }
            ++lc;
        }
        return result;
    }
    public static string ToString(IDictionary<string,IDictionary<string,object>> ini) {
        var sb = new StringBuilder();
        foreach (var sentry in ini) {
            sb.AppendLine("[" + sentry.Key + "]");
            var d = sentry.Value;
            foreach (var entry in d) {
                if (entry.Value is IList<string>) {
                    var l = ((IList<string>)entry.Value);
                    sb.AppendLine(string.Format("{0} = {1}", entry.Key, l[0]));
                    for (var i = 1; i < l.Count; ++i) {
                        sb.AppendLine("\t" + l[i]);
                    }
                    sb.AppendLine();
                } else
                    sb.AppendLine(string.Format("{0} = {1}", entry.Key, entry.Value));
            }
        }
        return sb.ToString();
    }
}

History

  • 13th December, 2021 - Initial submission
  • 13th December, 2021 - Bugfix

License

This article, along with any associated source code and files, is licensed under The MIT License