6장 - 리플렉션1

2014. 12. 21. 22:58IT Books/시작하세요 C# 프로그래밍

어셈블리 파일에는 메타데이터가  있음.

BCL에서 제공하는 리플렉션 (Reflection) 관련 클래스를 이용하면 메타데이터 정보를 얻을 수 있다.


--


닷넷에서는 프로세스 구조가 다음과 같다.


Exe 프로세스 = 1개의 공유 AppDomain + 1개의 기본 AppDomain

-> 기본은 1개지만 임의의 수를 만들 수 있다.


AppDomain이 만들어지면 그 내부에  어셈블리들이 로드되는데,

리플렉션을 이용하면 현재 AppDomain의 이름과 그 안에 로드된 어셈블리 목록을 구할 수 있다.


1
2
3
4
5
6
7
            AppDomain currDomain = AppDomain.CurrentDomain;
            Console.WriteLine(currDomain.FriendlyName);
            foreach (var item in currDomain.GetAssemblies())
            {
                Console.WriteLine(" " + item.FullName);
            }
 
cs

이렇게 구한 어셈블리는 모듈의 집합이다.
이런 어셈블리 내부 정보는 계층적으로 접근 할 수 있다.

어셈블리 > n개의 모듈 > n개의 타입 (클래스) > n개의 메서드, 필드, 프로퍼티, 이벤트

1
2
3
4
5
6
7
8
9
10
            foreach (var item in currDomain.GetAssemblies())
            {
                Console.WriteLine(" " + item.FullName);
 
                foreach (var module in item.GetModules())
                {
                    Console.WriteLine(" " + module.FullyQualifiedName);
                }
            }
 
cs


멤버를 유형별로 나눠서 볼 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
            foreach (var asm in currDomain.GetAssemblies())
            {
                Console.WriteLine(" " + asm.FullName);
 
                foreach (Type type in asm.GetTypes())
                {
                    Console.WriteLine(type.FullName);
 
                    // 클래스 생성자
                    foreach (ConstructorInfo ctorInfo in type.GetConstructors())
                    {
                        Console.WriteLine(ctorInfo.Name);
                    }
 
                    // 클래스 이벤트
                    foreach (EventInfo evInfo in type.GetEvents())
                    {
                        Console.WriteLine(evInfo.Name);
                    }
 
                    // 클래스 필드
                    foreach (FieldInfo fInfo in type.GetFields())
                    {
                        Console.WriteLine(fInfo.Name);
                    }
 
                    // 클래스 메서드
                    foreach (MethodInfo mInfo in type.GetMethods())
                    {
                        Console.WriteLine(mInfo.Name);
                    }
 
                    // 클래스 프로퍼티
                    foreach (PropertyInfo pInfo in type.GetProperties())
                    {
                        Console.WriteLine(pInfo.Name);
                    }
                }               
            }
 
cs

멤버를 유형별로 나눠서 볼 수도 있다.



C#코드가 빌드되어 어셈블리에 포함되는 경우

이런 모든 정보를 조회할 수 있는 기술을 리플렉션이라 한다.



AppDomain, Assembly


EXE 프로세스 내에서 CLR에 의해 구현된 격리 공간.

원한다면 별도로 생성하는 것도 가능.


AppDomain newDomain = new AppDomain.CreateDomain("testDomain");



AppDomain currentDomain = AppDomain.CurrentDomain;

현재 쓰레드가 속한 실행 중인 어셈블리가 속한 AppDomain.




AppDomain에 Assembly로드하기!


1
2
3
4
5
6
7
8
9
10
11
namespace ClassLibrary1
{
    public class Class1
    {
        public Class1()
        {
            Console.WriteLine(typeof(Class1).FullName + " : Created!");
        }
    }
}
 
cs

로딩 테스트를 위해 위와 같은 dll 프로젝트를 만들고


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Reflection;
using System.Runtime.Remoting;
 
namespace Reflection
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomain appDomain = AppDomain.CreateDomain("MyAppDomain");
 
            string dllPath = @"D:\Dropbox\work\csharp\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
 
            ObjectHandle objHandle = appDomain.CreateInstanceFrom(dllPath, "ClassLibrary1.Class1");
 
            AppDomain.Unload(appDomain);
        }
    }
}
 
cs
잘 로딩된다.
C / C++ 과는 달리 로딩한 dll만 따로 해제를 할 수 없다.
닷넷의 경우 AppDomain을 해제하는 경우만 해당 AppDomain에 속한 어셈블리가 모두 해제된다.

다만 기본 AppDomain에 로딩된 어셈블리는 프로세스가 종료될 때까지 해제 불가.

**
C++과는 달리 닷넷에서는 AppDomain은 서로 격리되므로
클래스에 정의된 static필드도 동일한 클래스가 서로 다른 AppDomain에 로딩되었다면
각각의 AppDomain에 존재할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
            // 고전적인 방법
            SystemInfo info = new SystemInfo();
            info.WriteInfo();
 
            // Type정보만으로 객체 생성. Activator 이용
            Type systemInfo1 = Type.GetType("Reflection.SystemInfo");
            object objInstance = Activator.CreateInstance(systemInfo1);
 
            // 기본 생성자를 호출함으로써 생성.
            Type systemInfo2 = Type.GetType("Reflection.SystemInfo");
            ConstructorInfo ctr = systemInfo2.GetConstructor(Type.EmptyTypes);
            object objInst2 = ctr.Invoke(null);
 
            // 메서드 호출
            MethodInfo methodInfo = systemInfo2.GetMethod("WriteInfo");
            methodInfo.Invoke(objInst2, null);
 
            FieldInfo fInfo = systemInfo2.GetField("is64Bit", BindingFlags.NonPublic | BindingFlags.Instance);
 
            // 기존 값 가져오기
            object oldValue = fInfo.GetValue(objInst2);
            // 새 값 쓰기
            fInfo.SetValue(objInst2, !Environment.Is64BitOperatingSystem);
 
            // 확인을 위해 메서드 호출
            methodInfo.Invoke(objInst2, null);
 
cs
객체를 생성하고 메서드를 호출하는 여러 방법.
리플렉션은 코드에서 보다시피
OOP의 캡슐화도 무시한다.