C#でオブジェクトをコピーする10の方法

C#オブジェクトコピーのイラストC#
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

C#プログラミング言語は、強力なオブジェクト指向の機能を提供し、開発者にとって不可欠なツールです。

この記事では、C#におけるオブジェクトの基本的な概念からその役割までを分かりやすく解説します。

オブジェクト指向プログラミング(OOP)は、現実世界の事物や概念をモデル化するための強力な方法であり、C#はその概念を取り入れています。

ここでは、オブジェクトがどのように機能し、プログラム内でどのように使用されるかについて理解を深めることを目指します。

●C#におけるオブジェクトとは

C#におけるオブジェクトは、プログラムの基本的な構成要素であり、データとそれを操作する方法(メソッド)をカプセル化します。

オブジェクトはクラスから生成され、クラスはオブジェクトの設計図として機能します。

オブジェクトを使用することで、プログラムの再利用性、保守性、拡張性が向上し、複雑な問題をより管理しやすい形で扱うことが可能になります。

○オブジェクトの基本的な概念

オブジェクト指向プログラミングにおける「オブジェクト」とは、データとそのデータに操作を加えるメソッドを組み合わせたものです。

C#では、オブジェクトはクラスのインスタンスとして実現されます。

クラスはオブジェクトの属性(プロパティ)と動作(メソッド)を定義し、オブジェクトはそのクラスに基づいて具体的なデータや状態を持ちます。

例えば、車をクラスとすると、その車の具体的なモデルや色などはオブジェクトのプロパティとして定義されます。

○C#でのオブジェクトの役割

C#においてオブジェクトは、プログラム内のデータとその処理を組織する重要な役割を担います。

オブジェクトを利用することで、データの抽象化とカプセル化が可能になり、プログラムの各部分が独立して機能することで、全体の管理が容易になります。

また、オブジェクトの継承とポリモーフィズムによって、コードの再利用性と拡張性が高まります。

これにより、開発者は効率的に安全で信頼性の高いソフトウェアを作成できるようになります。

●オブジェクトのコピー方法

オブジェクトのコピーは、C#プログラミングにおいて重要な概念です。

オブジェクトをコピーすることで、既存のデータを基に新しいデータを生成し、プログラムの柔軟性と効率を高めることができます。

しかし、単にオブジェクトの値をコピーするだけではなく、そのコピー方法によって異なる結果が得られることを理解することが重要です。

○浅いコピーと深いコピーの違い

浅いコピーと深いコピーは、オブジェクトをコピーする際の二つの主要な方法です。

浅いコピーは、オブジェクトの最上位レベルの値のみを新しいオブジェクトにコピーします。

つまり、元のオブジェクトと新しいオブジェクトは、同じ参照型のフィールドを共有します。

これに対して、深いコピーはオブジェクトのすべての階層を新しいオブジェクトに再帰的にコピーします。

この結果、元のオブジェクトと新しいオブジェクトは、参照型のフィールドにおいても独立したコピーを持つことになります。

プログラマーは、オブジェクトの特性とアプリケーションの要件に基づいて、どちらのコピー方法を選択するかを判断する必要があります。

●浅いコピーの実装

C#におけるオブジェクトの浅いコピーは、オブジェクトの参照型フィールドが元のオブジェクトと同じデータを参照するコピーを作成するプロセスです。

これは、オブジェクトの値型フィールドのみを新しいオブジェクトにコピーし、参照型フィールドの実際のデータは共有されるため、一方が変更されると他方にも影響します。

浅いコピーは、単純なデータ構造のコピーに適していますが、複雑なデータ構造の場合、予期しない副作用を引き起こす可能性があります。

○サンプルコード1:単純なフィールドコピー

下記のサンプルコードでは、C#における浅いコピーの一例を表しています。

ここでは、Personクラスを定義し、そのインスタンスを作成しています。

この例ではnewキーワードを使って新しいPersonオブジェクトを作成し、元のオブジェクトのフィールドを新しいオブジェクトにコピーしています。

public class Person
{
    public string Name;
    public int Age;
}

public class Example
{
    public static void Main()
    {
        Person original = new Person { Name = "John", Age = 30 };
        Person shallowCopy = new Person { Name = original.Name, Age = original.Age };

        Console.WriteLine("Original: " + original.Name + ", Age: " + original.Age);
        Console.WriteLine("Shallow Copy: " + shallowCopy.Name + ", Age: " + shallowCopy.Age);
    }
}

このコードでは、新しいPersonオブジェクトを作成し、元のオブジェクトのNameAgeの値を新しいオブジェクトに割り当てています。

しかし、この方法では参照型のフィールドがある場合、そのフィールドは元のオブジェクトと新しいオブジェクトで共有されることに注意が必要です。

○サンプルコード2:MemberwiseCloneを使用したコピー

MemberwiseCloneメソッドを使用すると、C#においてオブジェクトの浅いコピーをより簡単に作成することができます。

このメソッドは、オブジェクトの浅いコピーを作成し、そのすべてのフィールドの値を新しいオブジェクトにコピーします。

下記のサンプルコードでは、Personクラス内にCloneメソッドを定義し、MemberwiseCloneを使用してオブジェクトの浅いコピーを作成しています。

public class Person
{
    public string Name;
    public int Age;

    public Person Clone()
    {
        return (Person)this.MemberwiseClone();
    }
}

public class Example
{
    public static void Main()
    {
        Person original = new Person { Name = "John", Age = 30 };
        Person shallowCopy = original.Clone();

        Console.WriteLine("Original: " + original.Name + ", Age: " + original.Age);
        Console.WriteLine("Shallow Copy: " + shallowCopy.Name + ", Age: " + shallowCopy.Age);
    }
}

この例では、Cloneメソッドを呼び出すことで、元のPersonオブジェクトの浅いコピーを作成しています。

この方法は、元のオブジェクトのフィールドを個別にコピーするよりも効率的で、コードの重複を減らすことができます。

ただし、MemberwiseCloneを使用した浅いコピーでは、参照型フィールドが共有される点には変わりないため、その点を考慮する必要があります。

●深いコピーの実装

深いコピーは、オブジェクトのすべてのフィールドを新しいオブジェクトにコピーするプロセスです。

この方法では、元のオブジェクトと新しいオブジェクトが互いに独立しているため、一方での変更が他方に影響を与えません。

深いコピーは、特に複雑なオブジェクトや、参照型フィールドを多く含むオブジェクトを扱う際に有用です。

○サンプルコード3:シリアライズを使用した深いコピー

オブジェクトの深いコピーを実装する一つの方法は、シリアライズとデシリアライズを使用することです。

この方法では、オブジェクトを一旦バイト列にシリアライズし、その後、新しいオブジェクトとしてデシリアライズします。

下記のサンプルコードは、BinaryFormatterを使用した深いコピーの例を表しています。

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
public class Person
{
    public string Name;
    public int Age;
}

public class Example
{
    public static void Main()
    {
        Person original = new Person { Name = "John", Age = 30 };
        Person deepCopy = DeepCopy(original);

        Console.WriteLine("Original: " + original.Name + ", Age: " + original.Age);
        Console.WriteLine("Deep Copy: " + deepCopy.Name + ", Age: " + deepCopy.Age);
    }

    private static T DeepCopy<T>(T obj)
    {
        using (var memoryStream = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, obj);
            memoryStream.Position = 0;
            return (T)formatter.Deserialize(memoryStream);
        }
    }
}

このコードでは、PersonクラスがSerializable属性でマークされていることに注意してください。

これは、クラスのインスタンスをシリアライズ可能にするために必要です。

DeepCopyメソッドは、ジェネリックな方法で任意のオブジェクトに対して深いコピーを生成することができます。

○サンプルコード4:手動での深いコピー

深いコピーのもう一つの方法は、すべてのフィールドを手動で新しいオブジェクトにコピーすることです。

この方法は、特にシリアライズが利用できない場合や、特定のフィールドに対してカスタムのコピー処理を行いたい場合に適しています。

下記のサンプルコードは、Personクラスの深いコピーを手動で実行する例を表しています。

public class Person
{
    public string Name;
    public int Age;
    public Address HomeAddress;

    public Person DeepCopy()
    {
        return new Person
        {
            Name = this.Name,
            Age = this.Age,
            HomeAddress = new Address
            {
                Street = this.HomeAddress.Street,
                City = this.HomeAddress.City
            }
        };
    }
}

public class Address
{
    public string Street;
    public string City;
}

public class Example
{
    public static void Main()
    {
        Person original = new Person
        {
            Name = "John",
            Age = 30,
            HomeAddress = new Address { Street = "123 Main St", City = "Anytown" }
        };

        Person deepCopy = original.DeepCopy();

        Console.WriteLine("Original: " + original.Name + ", Address: " + original.HomeAddress.Street);
        Console.WriteLine("Deep Copy: " + deepCopy.Name + ", Address: " + deepCopy.HomeAddress.Street);
    }
}

このコードでは、Personオブジェクトが別のオブジェクトであるAddressを参照しているため、深いコピーを作成する際にはAddressオブジェクトも新たに生成しています。

これにより、元のPersonオブジェクトとは独立した完全なコピーが作成されます。

このように手動で深いコピーを実装することで、オブジェクトのコピー時の挙動を細かく制御することが可能です。

●C#におけるコピーの応用例

C#におけるオブジェクトのコピーは、さまざまな状況で利用できる多用途な技術です。

特に、コレクション内のオブジェクトのコピーは、データを操作する際に重要な役割を果たします。

リストやディクショナリなどのコレクションを扱う際に、各要素のコピーを適切に行うことで、データの整合性を保ちつつ、効率的な処理を実現することが可能です。

○サンプルコード5:リスト内のオブジェクトコピー

下記のサンプルコードは、リスト内のオブジェクトをコピーする方法を表しています。

この例では、Personオブジェクトのリストを作成し、それぞれの要素を新しいリストにコピーしています。

using System;
using System.Collections.Generic;

public class Person
{
    public string Name;
    public int Age;
}

public class Example
{
    public static void Main()
    {
        List<Person> originalList = new List<Person>
        {
            new Person { Name = "John", Age = 30 },
            new Person { Name = "Jane", Age = 25 }
        };

        List<Person> copiedList = new List<Person>();
        foreach (Person person in originalList)
        {
            copiedList.Add(new Person { Name = person.Name, Age = person.Age });
        }

        // 表示する
        foreach (Person person in copiedList)
        {
            Console.WriteLine("Name: " + person.Name + ", Age: " + person.Age);
        }
    }
}

この例では、元のリストoriginalListから新しいリストcopiedListへとPersonオブジェクトをコピーしています。

Personオブジェクトのフィールドを新しいインスタンスにコピーすることで、リスト内のオブジェクトが独立していることを保証しています。

○サンプルコード6:ディクショナリ内のオブジェクトコピー

ディクショナリ内のオブジェクトをコピーする場合、キーと値のペアを新しいディクショナリに適切にコピーする必要があります。

下記のサンプルコードでは、Personオブジェクトのディクショナリをコピーする方法を表しています。

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        Dictionary<string, Person> originalDict = new Dictionary<string, Person>
        {
            { "John", new Person { Name = "John", Age = 30 } },
            { "Jane", new Person { Name = "Jane", Age = 25 } }
        };

        Dictionary<string, Person> copiedDict = new Dictionary<string, Person>();
        foreach (KeyValuePair<string, Person> pair in originalDict)
        {
            copiedDict.Add(pair.Key, new Person { Name = pair.Value.Name, Age = pair.Value.Age });
        }

        // 表示する
        foreach (KeyValuePair<string, Person> pair in copiedDict)
        {
            Console.WriteLine("Key: " + pair.Key + ", Name: " + pair.Value.Name + ", Age: " + pair.Value.Age);
        }
    }
}

このコードでは、元のディクショナリoriginalDictから新しいディクショナリcopiedDictへとキーと値のペアをコピーしています。

この方法では、各Personオブジェクトを新しいインスタンスとしてコピーすることで、元のディクショナリと新しいディクショナリが独立していることを保証しています。

●コピー時の注意点

C#でオブジェクトをコピーする際には、いくつかの重要な注意点があります。

これらの点を理解し、適切に対応することで、バグや意図しない動作を防ぎ、効率的なプログラミングを実現することができます。

まず、コピーの種類(浅いコピーか深いコピーか)を適切に選択することが重要です。

浅いコピーはオブジェクトの最上位レベルのフィールドのみをコピーするため、参照型フィールドが同じデータを指し示すことになります。

これに対して深いコピーは、オブジェクトのすべてのフィールドを再帰的にコピーします。

オブジェクトの構造とプログラムの要件に応じて、適切なコピー方法を選択する必要があります。

次に、特に深いコピーを行う場合、オブジェクト内のすべてのフィールドが正しくコピーされることを保証するために、追加の注意が必要です。

これは、特にカスタムオブジェクトやコレクションを扱う場合に顕著です。

○パフォーマンスへの影響

オブジェクトのコピーは、特に大きなオブジェクトやデータ構造を扱う場合、パフォーマンスに影響を与える可能性があります。

深いコピーは、オブジェクトのすべてのフィールドをコピーするため、時間とメモリの両方において負荷がかかります。

したがって、パフォーマンスに影響が少ない方法を選択するか、コピー操作を最適化することが重要です。

○参照の扱い

オブジェクトをコピーする際には、参照の扱いにも注意を払う必要があります。

特に、浅いコピーを使用する場合、元のオブジェクトとコピーされたオブジェクトは、参照型のフィールドにおいて同じオブジェクトを共有します。

これにより、一方のオブジェクトで参照型フィールドを変更すると、他方のオブジェクトにも影響が及ぶ可能性があります。

したがって、このような副作用を避けるために、参照型フィールドの扱いには特に注意を払う必要があります。

●カスタマイズと最適化

C#でのオブジェクトコピーを最適化するためには、様々なカスタマイズが可能です。

コピー処理をカスタマイズすることで、特定のアプリケーションのニーズに合わせて、パフォーマンスを向上させたり、メモリ使用量を削減したりすることができます。

こうした最適化は、特に大規模なデータ構造やリソースを多く消費するアプリケーションで有効です。

○サンプルコード7:カスタムコピー関数の作成

カスタムコピー関数を作成することで、オブジェクトのコピー処理を特定のニーズに合わせて調整することができます。

下記のサンプルコードは、特定のフィールドのみをコピーするカスタムコピー関数の例を表しています。

public class Person
{
    public string Name;
    public int Age;
    public string Address;

    public Person CustomCopy()
    {
        return new Person { Name = this.Name, Age = this.Age };
    }
}

public class Example
{
    public static void Main()
    {
        Person original = new Person { Name = "John", Age = 30, Address = "123 Main St" };
        Person customCopy = original.CustomCopy();

        Console.WriteLine("Original: " + original.Name + ", Age: " + original.Age + ", Address: " + original.Address);
        Console.WriteLine("Custom Copy: " + customCopy.Name + ", Age: " + customCopy.Age + ", Address: " + (customCopy.Address ?? "Not Copied"));
    }
}

このコードでは、CustomCopyメソッドはNameAgeのフィールドのみを新しいPersonオブジェクトにコピーしており、Addressフィールドはコピーされていません。

これにより、不要なデータのコピーを避けることができます。

○サンプルコード8:コピー処理の最適化

大量のデータや複雑なオブジェクトを扱う場合、コピー処理を効率化することが重要です。

下記のサンプルコードは、大量のオブジェクトを効率的にコピーする方法を表しています。

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        List<Person> largeList = GenerateLargeListOfPeople();

        var start = DateTime.Now;
        List<Person> copiedList = new List<Person>();
        foreach (Person person in largeList)
        {
            copiedList.Add(new Person { Name = person.Name, Age = person.Age });
        }
        var end = DateTime.Now;

        Console.WriteLine("Time taken for copying: " + (end - start).TotalMilliseconds + " ms");
    }

    private static List<Person> GenerateLargeListOfPeople()
    {
        var list = new List<Person>();
        for (int i = 0; i < 10000; i++)
        {
            list.Add(new Person { Name = "Person " + i, Age = 30 });
        }
        return list;
    }
}

このコードでは、大量のPersonオブジェクトを含むリストを生成し、それらを新しいリストにコピーしています。

コピー処理の時間を計測することで、パフォーマンスの最適化を行う際の基準とすることができます。

まとめ

この記事では、C#におけるオブジェクトのコピー方法について詳しく解説しました。

オブジェクトのコピーは、プログラミングにおいて非常に一般的な操作であり、多くのアプリケーションで必要とされます。

C#でのオブジェクトコピーは、時には複雑であることもありますが、この記事を通じて基本的な概念と実践的なテクニックを習得することができたかと思います。

これらの知識を活用することで、より効率的かつ安全なC#プログラミングが可能になるでしょう。