안녕하세요. 명월입니다.
이 글은 C#에서 Reflection을 통한 클래스 복제에 대한 설명입니다.
디자인 패턴중에 프로토 타입 패턴이 있는데 이 패턴은 클래스를 복제하는 패턴입니다.
링크 - [Design pattern] 프로토타입 패턴 (Prototype pattern)
public class Node
{
public int Data { get; set; }
// 클래스 복제
public Node Clone()
{
// Object 클래스에 protected 형태로 존재한다.
return this.MemberwiseClone() as Node;
}
}
class Program
{
static void Main(string[] args)
{
Node node = new Node();
Node node1 = node.Clone();
// node과 node1의 메모리 해쉬코드가 다르기 때문에 힙에 선언된 클래스는 다른 클래스이다.
Console.WriteLine("node hashcode - " + node.GetHashCode());
Console.WriteLine("node1 hashcode - " + node1.GetHashCode());
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
위 결과를 보면 MemberwiseClone메서드를 통해서 클래스를 복제해서 HashCode값이 다르다는 것을 확인할 수 있습니다. 이는 C++에서 memcpy와 같은 형식의 동작입니다.
그런데 C#에서는 이 클래스 복제를 클래스 내부에서는 가능한데(위 예제를 보면 Node클래스 안에서 MemberwiseClone를 사용함), 외부에서는 불가능합니다.
즉, 위 예제의 Node클래스 안에 Clone메서드가 없다면 일일이 변수값을 옮겨 담아야 하고, 또 private의 변수가 있을 경우에는 복제가 되지 않겠네요.
public class Entity
{
public String Data { get; set; }
}
public class Node
{
private int privatedata;
public int Data { get; set; }
public int Data1 { get; set; }
public Entity Entity { get; set; }
public void SetData(int data)
{
privatedata = data;
}
public void Print()
{
Console.WriteLine("Data - " + Data);
Console.WriteLine("Data1 - " + Data1);
Console.WriteLine("privatedata - " + privatedata);
Console.WriteLine("Entity - " + Entity.Data);
Console.WriteLine();
}
}
class Program
{
static Node CopyNode(Node node)
{
Node ret = new Node();
ret.Data = node.Data;
ret.Data1 = node.Data1;
// 이건 클래스 복제가 아닌 단순히 포인터를 옮겨 담는 것일뿐.!!
ret.Entity = node.Entity;
//private 변수는 어떻게 복제를 하지?
return ret;
}
static void Main(string[] args)
{
Node node = new Node();
node.SetData(1);
node.Data = 2;
node.Data1 = 3;
node.Entity = new Entity();
node.Entity.Data = "Hello world";
// 클래스 복제..
Node node1 = CopyNode(node);
Console.WriteLine("node");
node.Print();
Console.WriteLine("node1");
node1.Print();
// 포인터만 옮겨 담았기 때문에 node1의 entity를 수정하면 node의 entity까지 수정이 되어 버린다.
node1.Entity.Data = "Modify!!";
// node의 entity값을 보면 역시나 Modify로 출력이 되어버린다.
Console.WriteLine("node");
node.Print();
Console.WriteLine("node hashcode - " + node.GetHashCode());
Console.WriteLine("node1 hashcode - " + node1.GetHashCode());
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
사실 클래스 복제할 때 위와 같이 작성하는 사람이 의외로 많습니다. 어찌어찌 private도 데이터를 넣을 수 있다고 해도 만약에 Node클래스가 수정이 된다면 그에 맞추어 CopyNode함수도 수정해야 겠네요.
예로 int Data2가 추가되면 CopyNode에도 Data2를 복제하는 식을 넣어야 합니다. 또 위 함수의 경우는 int 같은 자료형이라면 문제가 없겠지만 class형이라고 하면 복제가 아닌 포인터를 옮겨 담는 것 뿐이라 완벽한 복제가 되지 않을 확율도 높습니다.
그래서 이걸 해결할 수 있는 방법은 Reflection을 이용해서 복제하는 방법이 있습니다.
using System;
using System.Reflection;
namespace Example
{
public class Entity
{
public String Data { get; set; }
}
public class Node
{
private int privatedata;
public int Data { get; set; }
public int Data1 { get; set; }
public Entity Entity { get; set; }
public void SetData(int data)
{
privatedata = data;
}
public void Print()
{
Console.WriteLine("Data - " + Data);
Console.WriteLine("Data1 - " + Data1);
Console.WriteLine("privatedata - " + privatedata);
Console.WriteLine("Entity - " + Entity.Data);
Console.WriteLine();
}
}
class Program
{
static T CopyNode<T>(T node)
{
Type type = node.GetType();
T clone = (T)Activator.CreateInstance(type);
// 클래스 내부에 있는 모든 변수를 가져온다.
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
// 변수가 Class 타입이면 그 재귀를 통해 다시 복제한다. 단, String은 class이지만 구조체처럼 사용되기 때문에 예외
if (field.FieldType.IsClass && field.FieldType != typeof(String))
{
// 새로운 클래스에 데이터를 넣는다.
field.SetValue(clone, CopyNode(field.GetValue(node)));
continue;
}
// 새로운 클래스에 데이터를 넣는다.
field.SetValue(clone, field.GetValue(node));
}
return clone;
}
static void Main(string[] args)
{
Node node = new Node();
node.SetData(1);
node.Data = 2;
node.Data1 = 3;
node.Entity = new Entity();
node.Entity.Data = "Hello world";
// 클래스 복제.
Node node1 = CopyNode(node);
Console.WriteLine("node");
node.Print();
Console.WriteLine("nod1");
node1.Print();
// node1의 Entity가 수정이 되어도 Entity 클래스가 복제가 되었기 때문에 node에 영향이 없다.
node1.Entity.Data = "Modify!!";
Console.WriteLine("node");
node.Print();
Console.WriteLine("node hashcode - " + node.GetHashCode());
Console.WriteLine("node1 hashcode - " + node1.GetHashCode());
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
}
여기까지 C#에서 Reflection을 통한 클래스 복제에 대한 설명이었습니다.
궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.
'Development note > C#' 카테고리의 다른 글
[C#] 로그 라이브러리(log4net) (2) | 2019.08.23 |
---|---|
[C#] 날짜 포맷 (2) | 2019.08.06 |
[C#] 숫자 포맷 (돈 표시 및 소수점 이하 표시) (0) | 2019.08.05 |
[C#] XML를 XPath를 이용해서 다루는 방법 (0) | 2019.08.02 |
[C#] String 보간법(interpolation) (0) | 2019.07.05 |
[C#] 직렬화 (Serialization) (0) | 2019.06.27 |
[C#] ini 환경 설정 파일을 다루는 방법 (0) | 2019.06.26 |
[C#] 환경설정 파일을 다루는 방법(System.Configuration) (0) | 2019.06.26 |