はじめに 「指定行取得」は“長いテキストから、ピンポイントで1行だけ抜き出す”技
ログファイル、設定ファイル、外部システムからのテキスト——
業務では「長いテキストの中から、n行目だけ欲しい」という場面がよくあります。
例えば、
- エラーが起きた「123行目」の内容を表示したい
- ユーザーに「5行目を修正してください」と言うために、その行を抜き出したい
- 固定フォーマットのファイルで「2行目はヘッダ、3行目からデータ」と決まっている
こういうときに使うのが 指定行取得(GetLine)ユーティリティ です。
ここでは、初心者向けに、
改行コードと行番号の前提
シンプルな「n行目取得」メソッド
範囲外のときの扱い(例外にするか、nullにするか)
実務で使いやすい Try パターンや、行番号付き構造との連携
を、例題付きでかみ砕いて説明していきます。
前提整理:行番号は「1始まり」、改行コードは「統一」して考える
行番号は「1行目=1」として扱う
人間が「3行目」と言うとき、
それは「0,1,2…」ではなく「1,2,3…」の世界の話です。
なので、指定行取得ユーティリティも、
- 1行目 → lineNumber = 1
- 2行目 → lineNumber = 2
という 1始まり(1-based) で設計するのが自然です。
内部的には配列のインデックスが 0 から始まるので、lineNumber - 1 という変換をしてあげればOKです。
改行コードは先に統一しておく
前の「改行コード統一」「行分割」の話ともつながりますが、
指定行取得も、結局は「行分割した結果の中から1つ選ぶ」処理です。
なので、まずは改行コードをLF(\n)に統一してから行分割する、という方針にしておくと安定します。
public static class NewLineNormalizer
{
public static string ToLf(string? text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
string normalized = text.Replace("\r\n", "\n");
normalized = normalized.Replace("\r", "\n");
return normalized;
}
}
C#このユーティリティを前提にして、指定行取得を考えていきます。
基本形:指定行を取得する GetLine メソッド
シンプルな実装(範囲外なら null を返す)
まずは、「n行目を文字列として返す。範囲外なら null」という、
扱いやすい基本形を作ってみます。
using System;
public static class LineAccessor
{
public static string? GetLine(string? text, int lineNumber)
{
if (string.IsNullOrEmpty(text))
{
return null;
}
if (lineNumber <= 0)
{
throw new ArgumentOutOfRangeException(nameof(lineNumber), "行番号は1以上を指定してください。");
}
string normalized = NewLineNormalizer.ToLf(text);
string[] lines = normalized.Split('\n');
int index = lineNumber - 1;
if (index < 0 || index >= lines.Length)
{
return null;
}
return lines[index];
}
}
C#ここでやっていることを、順番に言葉で追うとこうなります。
- テキストが null または空なら、行は存在しないので null を返す。
- 行番号が 0 以下なら、そもそもおかしいので
ArgumentOutOfRangeExceptionを投げる。 - 改行コードをLFに統一する。
Split('\n')で行ごとに分割する。- 「行番号 − 1」を配列インデックスとして計算する。
- 範囲外なら null、範囲内ならその行の文字列を返す。
動作例
string text = "A\r\nB\r\nC";
Console.WriteLine(LineAccessor.GetLine(text, 1)); // "A"
Console.WriteLine(LineAccessor.GetLine(text, 2)); // "B"
Console.WriteLine(LineAccessor.GetLine(text, 3)); // "C"
Console.WriteLine(LineAccessor.GetLine(text, 4)); // null
C#ここでのポイントは、
- 行番号は1始まり
- 存在しない行を指定したときに「例外ではなく null」を返す
という挙動にしていることです。
Tryパターン:成功・失敗を bool で返す
例外を投げたくないときの書き方
業務コードでは、「存在しない行を指定することもあり得る」ケースが多いです。
そのたびに例外を投げると、パフォーマンスや可読性に影響が出ます。
そこでよく使われるのが、Tryパターンです。
public static class LineAccessor
{
public static bool TryGetLine(string? text, int lineNumber, out string? line)
{
line = null;
if (string.IsNullOrEmpty(text))
{
return false;
}
if (lineNumber <= 0)
{
return false;
}
string normalized = NewLineNormalizer.ToLf(text);
string[] lines = normalized.Split('\n');
int index = lineNumber - 1;
if (index < 0 || index >= lines.Length)
{
return false;
}
line = lines[index];
return true;
}
}
C#使い方はこうなります。
if (LineAccessor.TryGetLine(text, 3, out var line))
{
Console.WriteLine($"3行目: {line}");
}
else
{
Console.WriteLine("3行目は存在しません。");
}
C#このスタイルだと、
- 例外を使わずに「取れた/取れなかった」を判定できる
- out パラメータで結果を受け取れる
ので、業務ロジックに組み込みやすくなります。
行番号付き構造との連携(ログやエラー表示に便利)
行番号+テキストをまとめて扱う
「指定行取得」とセットでよく出てくるのが、
「何行目かも一緒に扱いたい」というニーズです。
例えば、エラー表示で、
エラー:5行目に不正な値があります
5行目の内容:XXX,YYY,ZZZ
のように出したい場合、
行番号と行テキストをセットで持っておくと便利です。
public readonly record struct LineInfo(int LineNumber, string Text);
public static class LineAccessorWithInfo
{
public static LineInfo? GetLineInfo(string? text, int lineNumber)
{
string? line = LineAccessor.GetLine(text, lineNumber);
if (line is null)
{
return null;
}
return new LineInfo(lineNumber, line);
}
}
C#使い方のイメージはこうです。
var info = LineAccessorWithInfo.GetLineInfo(text, 5);
if (info is { } lineInfo)
{
Console.WriteLine($"行番号: {lineInfo.LineNumber}");
Console.WriteLine($"内容 : {lineInfo.Text}");
}
else
{
Console.WriteLine("指定された行は存在しません。");
}
C#こうしておくと、「行番号」と「行内容」をセットで扱えるので、
ログ出力やエラー表示が書きやすくなります。
実務での使いどころと注意点
よくある使いどころ
指定行取得は、業務でこんな場面によく登場します。
- 外部から受け取ったファイルの「ヘッダ行」「データ開始行」を取り出す
- ログファイルの特定行だけを抜き出して調査するツール
- ユーザーに「n行目のエラー」を知らせるときに、その行の内容を一緒に表示する
例えば、「1行目はヘッダ、2行目はタイトル、3行目からデータ」という仕様なら、
string header = LineAccessor.GetLine(text, 1) ?? "";
string title = LineAccessor.GetLine(text, 2) ?? "";
C#のように書けます。
パフォーマンスを意識するなら「何度も Split しない」
巨大なテキストに対して、GetLine(text, 1), GetLine(text, 2), GetLine(text, 3)…と何度も呼ぶと、
そのたびに Split が走ってしまい、無駄が増えます。
そういう場合は、
- 一度だけ行分割してリストにしておく
- そのリストに対してインデックスアクセスする
という設計にしたほうが効率的です。
string normalized = NewLineNormalizer.ToLf(text);
string[] lines = normalized.Split('\n');
// あとは lines[0], lines[1], ... を直接使う
C#指定行取得ユーティリティは「簡単に使える窓口」として用意しつつ、
重い処理では「事前に行配列を作る」などの工夫をするとバランスが良くなります。
まとめ 「指定行取得ユーティリティ」は“長いテキストから、欲しい1行を安全に抜き出すピンセット”
指定行取得は、一見シンプルですが、
- 行番号は1始まりで扱う
- 改行コードの違いを吸収してから行分割する
- 範囲外のときの挙動(例外/null/false)を決める
- パフォーマンスが気になる場合は、事前に行配列を作る
といったポイントを押さえておくと、
業務コードの中で「安心して使える小さな道具」になります。
ここまで理解できれば、「なんとなく Split してインデックスを触っている」段階から一歩進んで、
“仕様に沿った行番号と改行コードを意識した、指定行取得ユーティリティ”を、
自分のC#コードの中にきちんと組み込めるようになっていきます。
