1.   CodeDom Lambda 비교

 

 

CodeDom

Lambda

변수 선언

가능                                      

불가 (단 하나의 표현식만 가능)

함수 작성

사용자가 C# 문법을 알아야함

간단하게 식 사용

Math 및 기타

라이브러리 지원

지원

지원(lambda-parser 오픈소스 사용)

컴파일 오류

오류 위치 확인 가능

오류 위치 확인 어려움

컴파일 형태

소스코드->CodeDom 그래프->IL 컴파일 (개발 복잡)

1.     Dll 파일 형태

2.     메모리 형태

소스코드->Expression tree->IL 컴파일

(소스코드 관리 편함)

1.     메모리 형태

호출 속도

1.     Func Delegate로 컴파일시 빠름, 단 함수 형태가 정의 되어있어야함

2.     DynamicInvoke는 느리지만 유연함

 

 

 

 

 

 

 

1.1 변수 선언

Lambda는 단 하나의 표현식만 가능하다. 모든 클래스나 메소드, 또는 선언문은 위해 설계되지 않아서 변수 사용은 불가능하다.

 

- CodeDom

 

-Lambda

 

 

 

 

1.2 Math 및 기타 라이브러리 사용

-CodeDom

 

 

-Lambda

 

 

 

1.3 컴파일 오류

-CodeDom

 

-Lambda

 

1.4 컴파일

CodeDOM CodeDOM 그래프, Lambda는 익스프레션 트리 형태로 변환되어 컴파일 된다. 익스프레션 트리는 CodeDOM과 크게 다를 바가 없지만, 두가지 차이가 있다.

첫째, 익스프레션 트리는 단 하나의 표현식만 가능하다. 모든 클래스나 메소드, 또는 선언문은 위해 설계되지 않았다.

둘째, C#은 언어에서 람다 식과 마찬가지로 직접적으로 익스프레션 트리를 지원한다.

 

-CodeDom

Windows에서는 개별 어셈블리 언로드를 지원하지 않는다. 대신 프로그램 도메인을 언로드하면 어셈블리도 같이 언로드가 된다. 하나의 프로세스에서는 주 프로그램 도메인(1)과 보조 프로그램 도메인(N)을 만들 수 있다. 보조 프로그램 도메인을 하나 생성하여 그 도메인에 어셈블리를 로드하여 사용하고, 재 컴파일시 도메인을 언로드하고 다시 로드하는 방식으로 수행하면 프로그램을 재시작하지 않고 사용자 정의 함수를 호출 할 수 있다.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CalcDynamicCompile {     class AssemblyHelper :IDisposable     {         AppDomain newAppDomain;         AssemblyLoader proxy;                  internal void LoadAssembly(string assemName)         {             AppDomainSetup setup = new AppDomainSetup();             setup.ShadowCopyFiles = "true";             newAppDomain = AppDomain.CreateDomain("newDomain"null, setup);             proxy = newAppDomain.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, 

typeof(AssemblyLoader).FullName) as AssemblyLoader;             proxy.LoadAssembly(assemName);         }         internal string LoadCode()         {             return proxy.LoadCode();         }                  internal void CreateInstance()         {             this.proxy.CreateInstance();                      }         internal void CreateDelegate(string methodName)         {             this.proxy.CreateDelegate(methodName);         }

        void IDisposable.Dispose()         {             proxy = null;             AppDomain.Unload(newAppDomain);             newAppDomain = null;         }         internal void Test(object[] param)         {             this.proxy.Test(param);         }     } }

 

 문제점은 도메인간에는 데이터가 공유되지 않기 때문에, MarshalByRefObject(응용 프로그램 도메인 간 경계를 넘어 개체에 엑세스할 수 있는 Abstract Class) Class를 상속 받은 개체에서만 사용자 정의 함수를 호출 할 수 있다. AppDomain으로 메소드 호출시에 Type이나 Assembly 등의 개체가 전달되거나 반환되면 상대방 AppDomain 측에서 해당 어셈블리의 로드가 자동으로 일어난다. 따라서 어셈블리의 동적 로드와 언로드의 목적 달성을 위해서는 이런 종류의 호출은 피해야 한다.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Diagnostics; namespace CalcDynamicCompile {     public class AssemblyLoader : MarshalByRefObject     {         Assembly assem;         object instance;

Delegate dele;         Func<floatfloatfloat> func;         public void LoadAssembly(string filename)         {             assem = Assembly.LoadFile(filename);         }         public override object InitializeLifetimeService()         {             // 이 객체가 원격 도메인 유휴 시간에 의해 자동으로 삭제되지 않게 함             return null;         }         internal string LoadCode()         {             string code = string.Empty;             string[] list = assem.GetManifestResourceNames();             if (list.Length > 0)             {                 using (System.Resources.ResourceReader sr = new System.Resources.ResourceReader(assem.GetManifestResourceStream(list[0])))                 {                     string resourceType;                     byte[] resourceData;                     sr.GetResourceData("code.txt"out resourceType, out resourceData);                     code = Encoding.ASCII.GetString(resourceData).Remove(0, 4);                 }             }             return code;         }         internal void CreateInstance()         {             Type type = assem.GetExportedTypes()[0];             instance = Activator.CreateInstance(type);         }         internal void CreateDelegate(string methodName)         {             Type type = assem.GetExportedTypes()[0];             MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); ;             List<Type> tArgs = new List<Type>();             foreach (var param in info.GetParameters())                 tArgs.Add(param.ParameterType);             tArgs.Add(info.ReturnType);             Type delDecltype = System.Linq.Expressions.Expression.GetDelegateType(tArgs.ToArray());             this.dele = Delegate.CreateDelegate(delDecltype, this.instance, info);             this.func = (Func<floatfloatfloat>)this.dele;         }         internal void Test(object[] param)         {             Stopwatch sw = Stopwatch.StartNew();             float res = 0;             for (int i = 0; i < 100000; i++)             {                 if (param == null)                 {                     res = (float)this.dele.DynamicInvoke(null);                 }                 else                 {                     res = (float)this.dele.DynamicInvoke(param);                 }             }             sw.Stop();             Console.WriteLine(string.Format("DynamicInvoke : {0}", sw.Elapsed.ToString()), res.ToString());             sw = Stopwatch.StartNew();             for (int i = 0; i < 100000; i++)             {                 res = this.func((float)param[0], (float)param[1]);             }             sw.Stop();             Console.WriteLine(string.Format("FuncInvoke : {0}", sw.Elapsed.ToString()), res.ToString());             sw = Stopwatch.StartNew();             for (int i = 0; i < 100000; i++)             {                 res = ((float)param[0] * (float)param[1]) / 2 + 3 - 2 * 321;             }             sw.Stop();             Console.WriteLine(string.Format("Call : {0}\n", sw.Elapsed.ToString()), res.ToString());         }         float Test(float a, float b)         {             return (a * b) / 2 + 3 - 2 * 321;         }     }

 

 Code 저장 : 사용자가 작성한 Code를 저장하기 위해선 다른 Text파일로 저장 및 관리 해두는 방법과 Dll 파일의 EmbeddedResource 기능을 이용하는 방법이 있다.

컴파일시 ResourceWriter 개체를 이용해 Resource를 만들어서 사용자가 작성한 코드를 Resource 파일로 작성 후 Recource 파일을 어셈블리에 추가시키면 된다.

System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.IncludeDebugInformation = false;
parameters.GenerateInMemory = true;
 
System.Resources.ResourceWriter writer = new System.Resources.ResourceWriter("Resources.resx");
writer.AddResource("code.txt"Encoding.ASCII.GetBytes(code));
writer.Generate();
writer.Close();
parameters.EmbeddedResources.Add("Resources.resx");
 
 
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.ReferencedAssemblies.Add("System.Data.dll");
parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
 
parameters.OutputAssembly = DllName;
 
CompilerResults r = CodeDomProvider.CreateProvider("CSharp").CompileAssemblyFromSource(parameters, code);
if (r.Errors.HasErrors == true)
{
    CompilerError err = r.Errors[0];
    MessageBox.Show(string.Format("Line:{0}, Column:{1}, ErrorNumber{2}\r\nMessage:{3}"
       err.Line.ToString(), err.Column.ToString(), err.ErrorNumber.ToString(), err.ErrorText));
}

로드시에는 역으로 어셈블리에서 Resource를 검색 후 ResourceReader 개체를 이용해 Resource를 읽는다.

string code = string.Empty;
 
if (File.Exists(DllName))
{
    using (AssemblyHelper assem = new AssemblyHelper())
    {
        assem.LoadAssembly(Application.StartupPath + "\\" + DllName);
        code = assem.LoadCode();
    }
}
 
return code;

 

-Lambda

코드 형태의 문자열을 Lambda식으로 변경하는 기능은 .Net에서 제공해주지 않아서 직접구현하거나 오픈 소스를 이용해야한다. http://code.google.com/p/lambda-parser/ 오픈소스를 이용하면 응용 프로그램에 로드된 라이브러리는 모두 사용 할 수 있다. Parse 메서드는 String 형태의 Code와 파라미터의 타입 정보, 사용할 클래스의 NameSpace를 넘겨주면 Expression을 생성한다. 그 이후 Compile 메서드를 통해 Delegate를 생성한다.

 

string code = this.tbCode.Text.Trim().Split('=')[1];
if (string.IsNullOrEmpty(code))
    return;
code = this.tbCode.Text;
 
 
LambdaExpression ex = ExpressionParser.Parse(code, nullthis.parameters.ToArray(), 
           "System""System.Collections.Generic");
 
this.dele = ex.Compile();
 
this.func = (Func<floatfloatfloat>)dele;
 
MessageBox.Show("Success");

 

 

 

1.5 CodeDom, Lambda 테스트

테스트 결과

  컴파일 모드 : Release

테스트한 식 : (a * b) / 2 + 3 - 2 * 321

테스트 값 : float a = 2, float b = 3

호출 테스트 수 : 100,000

총 테스트 수: 6

 

CodeDom, Lambda 테스트 코드

Stopwatch sw = Stopwatch.StartNew();
 
float res = 0;
 
for (int i = 0; i < 100000; i++)
{
    if (param == null)
    {
        res = (float)this.dele.DynamicInvoke(null);
    }
    else
    {
        res = (float)this.dele.DynamicInvoke(param);
    }
}
sw.Stop();
 
Console.WriteLine(string.Format("DynamicInvoke : {0}", sw.Elapsed.ToString()), res.ToString());
 
 
sw = Stopwatch.StartNew();
 
for (int i = 0; i < 100000; i++)
{
    res = this.func((float)param[0], (float)param[1]);
}
 
sw.Stop();
 
Console.WriteLine(string.Format("FuncInvoke : {0}", sw.Elapsed.ToString()), res.ToString());
 
sw = Stopwatch.StartNew();
 
for (int i = 0; i < 100000; i++)
{
    res = ((float)param[0] * (float)param[1]) / 2 + 3 - 2 * 321;
}
 
sw.Stop();
 
Console.WriteLine(string.Format("Call : {0}\n", sw.Elapsed.ToString()), res.ToString());

 

 

CodeDom 결과 :

Dynamic

Func

CSharp

테스트1

0.0648687

0.0008836

0.0004886

테스트2

0.0615519

0.0008796

0.0004813

테스트3

0.0621032

0.0008879

0.0004833

테스트4

0.0618416

0.0008455

0.0004631

테스트5

0.0614814

0.0008925

0.0004909

테스트6

0.0619039

0.0008714

0.0004929

평균

0.062291783

0.00087675

0.00048335

Call 기준(배수)

128.8751078

1.813902969

1

1 CodeDom 테스트 결과

 

Lambda 결과 :

Dynamic

Func

CSharp

테스트1

0.0962379

0.0009402

0.0004741

테스트2

0.0890706

0.000969

0.0005181

테스트3

0.07660805

0.0009647

0.0005141

테스트4

0.065423

0.0010326

0.0004853

테스트5

0.0898079

0.0009634

0.0004621

테스트6

0.0904247

0.00097

0.000487

평균

0.084595358

0.000973317

0.000490117

CSharp 기준(배수)

172.6024926

1.985887714

1

2 Lambda 테스트 결과

1.6  결론

지금까지 CodeDom Lambda를 테스트 해보았다. 속도 테스트 결과 CodeDom이 약간 우세하였다. 내가 생각하는 장단점은 아래와 같다. 둘 중 하나를 선택하라면 Lambda식이 적합한 것 같다.

 

CodeDom의 장점은 변수의 선언 및 복잡한 식의 계산을 쉽게 할 수 있다는 것이다. 단점은 사용자가 C# 언어를 학습 해야 하고, 함수의 네이밍, 메모리 관리 등 개발이 복잡하다는 것이다.

Lambda의 장점은 간편한 식을 작성 할 수 있고, 사용자에게 친숙하고, 함수의 관리가 편하다. 단점은 변수의 선언 및 복잡한 식은 설정하기 어렵다.

 

프로젝트 : CalcDynamicCompile.zip 

 

 

+ Recent posts