なぜ、文字列の理解が重要なのか、その理由

この記事では、当社の新人エンジニア研修の参考にC#を解説します。

前回は配列の作成と使用について学びました。
今回は、文字と文字列の扱いについてです。コンピュータプログラム(すなわちそれを作った新人エンジニアであるあなた)とユーザーのコミュニケーション手段として文字列は非常に重要だからです。


1. 文字と文字列の違い

C#では、文字(char型)と文字列(string型)は別物です。混同してしまう新人エンジニアもいますが、気をつけましょう。

using System;

namespace Chap07
{
    public class Example01
    {
        public static void Main()
        {
            char c1 = 'あ';
            string str1 = "あ";

            Console.WriteLine(c1);   // 1文字
            Console.WriteLine(str1); // 文字列
        }
    }
}

<実行結果>

あ
あ

どちらも見た目は同じ「“あ”」ですが、c1 は1文字を表す値型char)であり、str1 は複数文字を扱える参照型string)です。


文字は数値として扱える]

using System;

namespace Chap07
{
    public class Example02
    {
        public static void Main()
        {
            char c1 = 'あ';
            int a = c1;  // 'あ'をintに変換
            Console.WriteLine(a);
        }
    }
}

<実行結果>

12354

これは 'あ' のUnicodeコードポイント(UTF-16での値)を10進数で表したものです。


文字列は内部的に“文字の配列”

英単語「string」には「糸」「ひも」という意味があり、文字が糸で連なるイメージです。
たとえば:

using System;

namespace Chap07
{
    public class Example03
    {
        public static void Main()
        {
            char[] charArray = { 'H', 'e', 'l', 'l', 'o' };
            string str = new string(charArray);
            Console.WriteLine(str);
        }
    }
}

stringは内部的には文字データを連ねた構造と考えられます(実際、C# stringはUTF-16エンコードの文字列を保持し、char[]に似た形で実装されています)。


2. stringクラス

C#のstringは.NETの参照型であり、System.Stringというクラスのエイリアスです。そのため、using System; だけで文字列をすぐに使えます。

インスタンス化

C#では文字列リテラルを使うのが一般的です。

string str1 = "Hello";
Console.WriteLine(str1);

C#では new string("Hello") も書けますが、あまり使いません。文字列リテラルを直接書いたほうが効率も良く、同じリテラルを使い回す「インターン化(interning)」も自動で行われます。


3. Equals() メソッド / == 演算子

参照の比較と内容の比較

string型に対する == 演算子は文字列の内容を比較します。さらに Equals() メソッドも内容比較に使えます。

using System;

namespace Chap07
{
    public class Example06
    {
        public static void Main()
        {
            string str1 = new string("Hello");
            string str2 = new string("Hello");
            Console.WriteLine(str1 == str2);       // true
            Console.WriteLine(str1.Equals(str2));  // true

            // 文字列リテラル
            string str3 = "Hello";
            string str4 = "Hello";
            Console.WriteLine(object.ReferenceEquals(str3, str4)); 
            // => trueになる可能性が高い(同じリテラル参照)
        }
    }
}

<実行結果>

true
true
true
  • str1str2 は異なるオブジェクトですが、C#の == は文字列の内容を比較するため true
  • Equals() メソッドも内容を比較するので true
  • object.ReferenceEquals(str3, str4) は参照そのものを比較し、両者が同じリテラルを指しているなら true になります。

注意: C#で文字列を比較する場合は==もOKですが、他の参照型(後述)では==は参照比較になるので混同しないようにしましょう。


4. イミュータブル(不変性)と文字列の連結

C#の string はイミュータブル(immutable)です。一度作った文字列オブジェクトは内部の文字を変更できません。

using System;

namespace Chap07
{
    public class Example08
    {
        public static void Main()
        {
            string str1 = "Hello";
            str1 = str1 + " World"; 
            Console.WriteLine(str1);
        }
    }
}

<実行結果>

Hello World

しかし、str1 + " World" の操作で元の "Hello" を書き換えたわけではありません。新しい "Hello World" という文字列が生成され、str1 はそちらを指すようになっただけです。

イミュータブルの影響

string s1 = "Hello";  // ①
string s2 = s1;       // ②
Console.WriteLine(object.ReferenceEquals(s1, s2)); // true(同じオブジェクトを参照している)

s1 = "World";         // ③ ← 新しい文字列リテラル
Console.WriteLine(object.ReferenceEquals(s1, s2)); // false

  • 文字列が「上書き」されるのではなく、新しく生成された"World"を参照し直しただけ
  • s2 は依然として "Hello" を参照

パフォーマンス

文字列連結を大量に繰り返す場合、イミュータブルゆえに生成と破棄が多くなり、パフォーマンスが悪化する恐れがあります。C#ではStringBuilderSystem.Text.StringBuilderクラス)を使うのが定番です。


5. string クラスの便利メソッド

C#のstringクラスには、文字列操作のための便利なメソッドが数多く用意されています。例えば:

メソッド / プロパティ説明使用例
Length (プロパティ)文字列の長さを取得str.Length
IndexOf(string)部分文字列が最初に出現する位置を返すstr.IndexOf("Java")
Contains(string)部分文字列が含まれているかを bool で返すstr.Contains("研修")
Replace(string, string)指定文字列を別の文字列に置換str.Replace("エンジニア", "SE")
Substring(int, int)一部の文字列を抜き出すstr.Substring(2,5)
ToUpper()/ToLower()大文字/小文字に変換str.ToUpper()
Trim()前後の空白を除去str.Trim()

サンプル

using System;

namespace Chap07
{
    public class Example10
    {
        public static void Main()
        {
            string str = "新人エンジニアのためのC#研修";

            // 文字列の長さ
            Console.WriteLine(str.Length);
            Console.WriteLine();

            // 部分文字列の検索
            Console.WriteLine(str.IndexOf("C#"));
            Console.WriteLine(str.IndexOf("Python")); // 見つからない -> -1
            Console.WriteLine();

            // 含まれるか?
            Console.WriteLine(str.Contains("研修"));   // True
            Console.WriteLine(str.Contains("Python")); // False
            Console.WriteLine();

            // 部分置換
            string str2 = str.Replace("エンジニア", "SE");
            Console.WriteLine(str2);
        }
    }
}

<実行結果>

15

10
-1

True
False

新人SEのためのC#研修
  • IndexOf("C#") → 10 (C#が文字列中で見つかった位置。0始まりなので先頭から10番目)
  • IndexOf("Python") → -1 (見つからない)
  • Replace("エンジニア", "SE")"新人SEのためのC#研修"

3桁カンマ区切り表示 (String.Format / ToString / Console.WriteLine)

C#では string.Format、あるいは $"{}"文字列補間ToString("N0") などで書式を指定できます。

using System;

namespace Chap07
{
    public class Example11
    {
        public static void Main()
        {
            int price = 123456789;
            Console.WriteLine(string.Format("{0:N0}", price)); 
            // または
            Console.WriteLine($"{price:N0}");
        }
    }
}

<実行結果>

123,456,789
123,456,789
  • "{0:N0}" は、小数点なしで3桁区切りを入れる書式指定
  • C#には String.Format のほか、文字列補間 $"{...}"price.ToString("N0") など多彩な書き方があります。

このように、C#でも書式設定は大事な機能です。業務システムでは金額を扱うことが多いため、よく使います。


6. クラスメソッド (static) とインスタンスメソッド

  • インスタンスメソッドstr.Length, str.Replace(... ), str.Contains(...)など。これは文字列オブジェクトstr)に対して「あなたはどれくらいの長さ?」「この部分を置換して」と呼びかけるイメージ。
  • staticメソッド (クラスメソッド) … string.Format(...)など。オブジェクトの内容に依存せず、あらかじめ定義された処理をクラス名から直接呼び出せます。
// インスタンスメソッドの例
string s = "新人エンジニアのためのC#研修";
Console.WriteLine(s.Length);  // sに対して「長さを教えて」と尋ねる

// staticメソッドの例
Console.WriteLine(string.Format("{0:N0}", 123456789));

  • C#なら Console クラスの WriteLine も同様に、静的メソッド(クラスメソッド)です。
  • 静的メソッドはクラス名.メソッド名で呼び、インスタンスメソッドは変数名.メソッド名で呼ぶ、という区別があります。

7. エスケープシーケンス

文字列リテラルの中で特殊な意味を持たせるには、C#ではバックスラッシュ \ を使います(Windows日本語環境では円記号に見える場合もある)。

エスケープシーケンス意味
\n改行 (LF)
\tタブ
\'シングルクォート ' を文字として表示
\"ダブルクォート " を文字として表示
using System;

namespace Chap07
{
    public class Example12
    {
        public static void Main()
        {
            Console.WriteLine("Hello\nWorld");
        }
    }
}


<実行結果>

Hello
World

このように \n によって改行が挿入されます。
C#では文字列リテラルに @ を付けた「逐語的文字列リテラル」(@"") なども使えますが、エスケープの扱いは特殊になります。


8. null 参照

C#の参照型変数(string, 配列やクラス)は、どのオブジェクトも参照していない特別な状態として null を代入できます。

using System;

namespace Chap07
{
    public class Example14
    {
        public static void Main()
        {
            string str1 = new string("ABC");
            string str2 = str1;
            str1 = null;
            Console.WriteLine(str2); // "ABC"
            str2 = null;
            Console.WriteLine(str2); // null
        }
    }
}

<実行結果>

ABC
null
  • "ABC" という文字列は当初 str1str2 が指していましたが、両者を null にすることで、どこからも参照されなくなりました。
  • C#ではガーベージコレクションにより、参照されなくなったオブジェクトは後でメモリから解放されます(ただし、いつ解放するかはランタイムが決定)。

null と 空文字の違い

  • null … 変数がどのオブジェクトも指していない状態
  • "" (空文字) … 長さ0の文字列オブジェクトを参照している状態

Webアプリケーションやデータベース操作では null チェックがよく必要になります。string.IsNullOrEmpty(...)string.IsNullOrWhiteSpace(...) のようなヘルパーメソッドも用意されています。

using System;

namespace Chap07
{
    public class Example15
    {
        public static void Main()
        {
            Console.Write("文字列を入力してください: ");
            string userInput = Console.ReadLine();

            // nullチェック
            if (userInput == null)
            {
                Console.WriteLine("入力がnullです。");
            }
            // 空文字チェック
            else if (userInput.Length == 0) // or userInput == ""
            {
                Console.WriteLine("入力が空です。");
            }
            else
            {
                Console.WriteLine("入力された文字列: " + userInput);
            }
        }
    }
}

Console.ReadLine()null を返すことは少ないですが、WebアプリなどではDBから null が返るケースも多いので注意が必要です。


    <まとめ:隣の人に正しく説明できたらチェックを付けましょう>

    □ 文字と文字列は別物 char (文字1つ) と string (複数文字)

    □ C#では文字列の内容比較に == もしくは Equals() メソッドを使う(他の型での==は参照比較に注意)

    □ 文字列はイミュータブルであるため、大量連結には StringBuilder が推奨

    string には Length, IndexOf, Contains, Replace など便利なメソッドがある

    □ 参照型に null を代入すると、オブジェクトを参照しない状態になる。ガーベージコレクションにより解放される(タイミングはランタイム依存)

    これで、C#の文字列の基本はマスターです。文字列操作はユーザーとのやり取りやデータ加工に欠かせないので、しっかり練習しておきましょう。

    次回は「staticメソッドを定義して処理を再利用する」などを学んでいきます!

    最後までお読みいただきありがとうございます。