はじめに 「省略文字列生成」は“長すぎる情報を、いい感じに切る”技
業務システムを作っていると、「全部は見せられないけど、雰囲気は伝えたい」という場面がよく出てきます。
ログに長いメッセージを全部出すと読めない、一覧画面でカラム幅に収まらない、ツールチップにだけ全文を出したい、などです。
そこで使うのが「省略文字列生成」、いわゆる「…で末尾を省略する」処理です。
例えば「これはとても長い説明文です」を「これはとても長い…」のように切るイメージです。
ここでは、初心者向けに、
「なぜ省略が必要になるのか」から始めて、
基本的な実装パターン、注意すべき境界条件、ユーティリティとしての設計まで、
順番にかみ砕いて説明していきます。
省略文字列の基本方針を決める
どんな仕様にしたいかを言葉にしてみる
まず、「省略文字列」と言ったときに、仕様を言葉で整理してみます。
最大長を決める。
文字列がその長さ以下なら、そのまま返す。
長さを超えているなら、「途中まで+省略記号(たとえば …)」にして返す。
例えば「最大10文字、末尾に … を付ける」という仕様なら、
「これはとても長い説明文です」は「これはとても長…」のようになります。
ここで大事なのは、「省略記号も含めて最大長に収めるかどうか」です。
多くの場合、「省略記号も含めて最大長以内」にするほうが扱いやすいです。
一番シンプルな実装パターン
末尾に “…” を付ける省略処理
まずは、半角前提でシンプルな「…ではなく ‘…’ を付ける」バージョンから見てみます。
public static class StringTruncateUtil
{
public static string TruncateWithDots(string? value, int maxLength)
{
if (maxLength <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength は 1 以上にしてください。");
}
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
if (value.Length <= maxLength)
{
return value;
}
const string suffix = "...";
if (maxLength <= suffix.Length)
{
return suffix.Substring(0, maxLength);
}
int takeLength = maxLength - suffix.Length;
string head = value.Substring(0, takeLength);
return head + suffix;
}
}
C#このメソッドがやっていることを、順番に言葉で追ってみます。
最大長が 0 以下なら、そもそもおかしいので例外にする。
null や空文字なら、空文字を返す。
元の文字列の長さが最大長以下なら、そのまま返す(省略不要)。
省略が必要な場合は、末尾に付ける文字列(ここでは “…”)を決める。
最大長が “…” より短い場合は、”..” や “.” のように、入る分だけ返す。
それ以外の場合は、「最大長 − suffix の長さ」だけ先頭から取り、その後ろに suffix を付ける。
これで、「最大長以内に収めつつ、省略されていることが分かる文字列」が作れます。
動作例でイメージを固める
Console.WriteLine(StringTruncateUtil.TruncateWithDots("これはテストです", 10));
// 長さが10以下ならそのまま(例: "これはテストです")
Console.WriteLine(StringTruncateUtil.TruncateWithDots("これはとても長い説明文です", 10));
// 例: "これはとても..." ("これはとても" + "..." で10文字以内)
Console.WriteLine(StringTruncateUtil.TruncateWithDots("短い", 2));
// 例: "短い"(そもそも2文字以下ならそのまま)
Console.WriteLine(StringTruncateUtil.TruncateWithDots("長い文字列", 2));
// 例: ".."(suffix の一部だけ入るパターン)
C#ここまでで、「とりあえず動く省略処理」がイメージできたと思います。
省略記号を “…”(1文字)にするバージョン
日本語UIでよく使う「三点リーダー」
日本語の画面では、”…” ではなく「…」(三点リーダー)を使うことも多いです。
この場合、suffix が1文字なので、計算が少しシンプルになります。
public static class StringTruncateUtil
{
public static string TruncateWithEllipsis(string? value, int maxLength)
{
if (maxLength <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxLength));
}
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
if (value.Length <= maxLength)
{
return value;
}
const string suffix = "…";
if (maxLength == 1)
{
return suffix;
}
int takeLength = maxLength - suffix.Length;
string head = value.Substring(0, takeLength);
return head + suffix;
}
}
C#このバージョンでは、「最大長が1なら ‘…’ だけ返す」というルールにしています。
最大長が2以上なら、「先頭から maxLength − 1 文字+’…’」という形になります。
マルチバイト文字と「見た目の幅」の話
Length と「見た目の幅」は必ずしも一致しない
ここまでの実装は、value.Length を使って「文字数」で制御しています。
C# の string.Length は「UTF-16 のコード単位数」なので、
日本語1文字も英字1文字も、だいたい「1」として数えられます(サロゲートペアなどの例外はありますが、まずは置いておきます)。
ただし、コンソールや固定幅フォントの世界では、
「全角は幅2、半角は幅1」として扱われることもあります。
「見た目の幅で揃えたい」場合は、単純な Length では足りません。
このレベルに踏み込むと、「全角・半角の判定」「幅の計算」といった別のユーティリティが必要になります。
最初の一歩としては、「文字数ベースでの省略」を押さえておけば十分です。
「見た目の幅で揃えたい」という要件が出てきたら、そのときに一段階深い実装を検討する、という順番で大丈夫です。
業務ユーティリティとしての設計ポイント
null や空文字の扱いを決めておく
業務コードでは、「null が来たらどうするか」を毎回考えたくありません。
省略ユーティリティの中で、ルールを決めておくと呼び出し側が楽になります。
上の例では、「null や空文字なら空文字を返す」という方針にしています。
これなら、呼び出し側で value ?? "" といったガードを書く必要がありません。
プロジェクトによっては、「null は null のまま返す」ほうがよい場合もあります。
その場合は戻り値を string? にして、null をそのまま返す実装に変えればOKです。
省略記号や最大長を「用途ごとに固定」したメソッドを用意する
毎回 TruncateWithEllipsis(text, 20) のように書いてもよいのですが、
用途ごとに「最大長」が決まっているなら、名前付きのメソッドにしておくと読みやすくなります。
例えば、一覧画面の「説明」カラムは最大30文字、
ログのメッセージは最大100文字、という仕様なら、次のようにラップできます。
public static class AppStringShortener
{
public static string ForListDescription(string? value)
=> StringTruncateUtil.TruncateWithEllipsis(value, 30);
public static string ForLogMessage(string? value)
=> StringTruncateUtil.TruncateWithEllipsis(value, 100);
}
C#呼び出し側は、「何文字にするか」ではなく「どの用途か」を意識して書けるようになります。
これは、後から仕様が変わったとき(「やっぱり40文字にしたい」など)にも効いてきます。
まとめ 「省略文字列ユーティリティ」は“全部は見せられないけど、ちゃんと伝えるためのカット編集”
省略文字列生成は、「長いから切る」というだけの話ではありません。
業務で本当に大事なのは、
最大長を決め、その中に「省略記号も含めて」収めること
短い文字列はそのまま返し、長いときだけ省略すること
null や空文字の扱いをユーティリティ側でルール化すること
用途ごとに「どの長さで切るか」を名前付きメソッドにしておくこと
といった設計の部分です。
ここまで理解できれば、「なんとなく Substring で切っている」段階から一歩進んで、
“ユーザーにも開発者にも分かりやすい、省略ルールを持った文字列ユーティリティ”を、
自分の手で設計・実装できるようになっていきます。

