昔のWindows系OSで設定用のファイルとしてiniファイルがありました。
[Section1]
Key1=Value1
Key2=Value2
[Section2]
AnotherKey=AnotherValue
こちらの書式を連想配列に変換してみたいと思います。
ソースコード
ファイル名:Context.cs
namespace Interpreter02;
// コンテキスト
public class Context
{
public Dictionary<string, string> Data{ get; } = new Dictionary<string, string>();
public string CurrentSection { get; set; } = "";
}
DataがDictionaryですので、結果となる連想配列として、こちらに要素が追加されていくと思われます。
CurrentSectionは現在のセクションがセットされます。
ファイル名:IExpression.cs
namespace Interpreter02;
// ノードのインターフェイス
interface IExpression
{
void Interpret(Context context);
}
メソッドはInterpret()の一つだけ。今回のサンプルの場合、contextのDataにパースした要素を追加する処理が実装されると思われます。contextの実態は一つで複数のInterpret()を渡り歩いて直近の内容を保持するイメージになるかと思います。(そんな使われ方をする変数に関するデザインパターンもあったような?)
ファイル名:SectionExpression.cs
namespace Interpreter02;
// 終端記号: セクション名
public class SectionExpression : IExpression
{
private readonly string _sectionName;
public SectionExpression(string sectionName)
{
_sectionName = sectionName;
}
public void Interpret(Context context)
{
context.CurrentSection = _sectionName;
}
}
[]で囲まれたセクションを処理するExpressionに成ります。
InterpretではcontextのCurrentSectionを代入(更新)しています。
ファイル名:KeyValueExpression.cs
namespace Interpreter02;
// 終端記号: キーと値のペア
class KeyValueExpression : IExpression
{
private readonly string _key;
private readonly string _value;
public KeyValueExpression(string key, string value)
{
_key = key;
_value = value;
}
public void Interpret(Context context)
{
if (!string.IsNullOrEmpty(context.CurrentSection))
{
context.Data[$"{context.CurrentSection}:{_key}"] = _value;
}
}
}
=で区切られたキーと値のペアを処理するExpressionになります。
InterpretではcontextのData連想配列にkeyとvalueをセット(追加)しています。
ファイル名:IniFileExpression.cs
namespace Interpreter02;
// 非終端記号: INIファイル全体
class IniFileExpression : IExpression
{
private readonly List<IExpression> _expressions = new List<IExpression>();
public void AddExpression(IExpression expression)
{
_expressions.Add(expression);
}
public void Interpret(Context context)
{
foreach (var expression in _expressions)
{
expression.Interpret(context);
}
}
}
メンバーにListのIExpressionの_expressionsがあり、パースして生成したKeyValueExpressionやSectionExpressionを追加するAddExpression()メソッドを提供します。
Interpretは_expressionsに登録されたExpressionを順番に全て取出し、Interpret()実行しています。クライアントが操作するインターフェイスはこちらのクラスに成りそうです。
ファイル名:IniFileInterpreter.cs
namespace Interpreter02;
// クライアント
class IniFileInterpreter
{
public Dictionary<string, string> Interpret(string filePath)
{
var context = new Context();
var expression = Parse(filePath);
expression.Interpret(context);
return context.Data;
}
private IniFileExpression Parse(string filePath)
{
var iniFileExpression = new IniFileExpression();
var lines = File.ReadAllLines(filePath)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrEmpty(line) && !line.StartsWith(";")); // 空行とコメントを無視
foreach (var line in lines)
{
if (line.StartsWith("[") && line.EndsWith("]"))
{
var sectionName = line.Substring(1, line.Length - 2);
iniFileExpression.AddExpression(new SectionExpression(sectionName));
}
else if (line.Contains("="))
{
var parts = line.Split('=', 2);
var key = parts[0].Trim();
var value = parts[1].Trim();
iniFileExpression.AddExpression(new KeyValueExpression(key, value));
}
}
return iniFileExpression;
}
}
iniファイルのパース部分で、結果をIniFileExpressionに追加しています。
ファイル名:Program.cs
namespace Interpreter02;
public class Program
{
static void Main()
{
// INIファイルの内容を記述(実際のファイルパスに置き換えてください)
string iniContent = @"
[Section1]
Key1=Value1
Key2=Value2
[Section2]
AnotherKey=AnotherValue
";
string tempFilePath = "temp.ini";
File.WriteAllText(tempFilePath, iniContent);
var interpreter = new IniFileInterpreter();
var data = interpreter.Interpret(tempFilePath);
Console.WriteLine($"{data.GetType()}");
foreach (var pair in data)
{
Console.WriteLine($"Key: {pair.Key}, Value: {pair.Value}");
}
File.Delete(tempFilePath); // 一時ファイルを削除
}
}
プログラムの開始位置に成ります。
前処理として実行用のiniファイル作成、後処理で削除がありますが、メインはiniファイルを引数にinterpreter.Interpret()を実行すると結果が連想配列としてdataにセットされている部分に成ります。
実行結果
Key: Section1:Key1, Value: Value1
Key: Section1:Key2, Value: Value2
Key: Section2:AnotherKey, Value: AnotherValue
感想
C#は静的な型なプログラミング言語で、変数には型があり基本的に異なる型は代入することが出来ません。
しかしinterfaceで定義された型とその派生されたオブジェクトは異なる型のオブジェクトであっても同じ変数にセットすることが出来ます。今回の場合、本来異なる型のSectionExpressionとKeyValueExpressionをList<IExpression>にセットすることが出来ます。動的型のスクリプト言語などは配列などに異なる型のオブジェクトを代入できますが(便利だけど都合が悪い場面も多い)、静的型を崩さずに、それと同じような振る舞いを実現しています。
iniファイルの書式はシンプルなのでInterpreterパターンの恩恵は薄いのですが、Interpret()メソッド実行すると、順番に処理が実行される仕組みになっており、手続き型のコードが上から順番に実行されるイメージに近いと感じになります。内部的にListの配列で順番に実行されるだけですが、頑張れば分岐やループなどの処理も表現できるようになるのでは無いかと思います。
コメント