스크립트 엔진의 단위 테스트 코드를 손보다가 버그를 발견했다. 말이 스크립트 엔진이지 스크립트 언어를 따로 구현해 쓰지 않고 닷넷 프레임워크를 지원하는 C#, VB.NET 등의 언어를 그냥 쓰는데 아무래도 서버 개발자가 스크립트를 직접 개발하는 초기 단계다 보니 C#이 주 언어가 됐다. 그런 탓에 단위 테스트 코드도 C#으로 작성했는데 이런 식이었다.
using (StreamWriter sw = new StreamWriter(File.Open(path, FileMode.Append))) { sw.WriteLine(ByeCount); }
이 코드를 간결하게 한답시고 var
키워드를 적용했다.
using (var sw = new StreamWriter(File.Open(path, FileMode.Append))) { sw.WriteLine(ByeCount); }
여기까진 좋았다. 닷넷 프레임워크 3.5를 도입했으니 최신 기능을 써서 나쁠 건 없다. 그러나 문제는 스크립트 엔진이 이 코드를 런타임에 컴파일하다 발생했다. var
키워드를 인식 못하는지 컴파일러 예외가 튀어나왔다. 이상하다 싶어 단위 테스트에 중단점을 걸고 살펴봤다. 원인은 컴파일러 버전이 3.5가 아닌 2.0이라는 점이었다.
// language는 "C#", "Visual Basic .NET" 같은 문자열이다. CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider(language);
원래는 위의 코드처럼 필요한 컴파일러 인스턴스를 불러왔다. 그러나 이 방식으론 var
키워드 등을 지원하는 C# 컴파일러 3.5가 나오지 않는다. 이 코드를 담은 어셈블리가 닷넷 프레임워크 3.5를 이용해 작성됐더라도 말이다. 컴파일러 버전 3.5가 필요하다면 이를 명시해야 한다. 그 방법은 크게 두 가지인데 Compiling with CodeDomProvider doesn’t allow new features of C# or VB란 글에 잘 정리되어 있다.
방법 1 – app.config에 명시하기
예제만 보면 한눈에 무슨 이야기인지 안다. 더 이상의 설명은 불필요하다.
<system.codedom> <compilers> <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" compilerOptions="/optimize" warningLevel="1" > <providerOption name="CompilerVersion" value="v3.5" /> </compiler> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" compilerOptions="/optimize" warningLevel="1" > <providerOption name="CompilerVersion" value="v3.5" /> </compiler> </compilers> </system.codedom>
방법 2 – 컴파일러와 그 버전을 명시하기
이 방법도 아주 간결하다.
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
방법 3 – 좀더 세련된 방법
좀더 세련된 방법이란 방법 2를 개선한 것이다. CodeDomProvider.CreateProvider(language)
를 사용했던 처음 코드는 간결했다. VB.NET을 쓸지 C#을 쓸지 명시해주면 알아서 필요한 CodeDomProvider
인스턴스를 반환한다. 그러나 불행히 CreateProvider
메서드는 CompilerVersion
값을 받는 매개변수가 없다. 그러니 if
문을 작성해 language
값에 따라 CSharpCodeProvider
인스턴스를 생성하던가 해야 한다. 그러나 이렇게 지저분한 코드는 짜증만 돋군다. 여기 조금 더 나은 방법이 있다.
CodeDomProvider codeDomProvider = GetCodeDomProvider(language); private static CodeDomProvider GetCodeDomProvider(string language) { var providerOptions = new Dictionary<string, string> { {"CompilerVersion", "v3.5"} }; Type codeDomProviderType = CodeDomProvider.GetCompilerInfo(language).CodeDomProviderType; ConstructorInfo constructor = codeDomProviderType.GetConstructor( new Type[] { typeof(IDictionary<string, string>) } ); return (CodeDomProvider)constructor.Invoke(new object[] { providerOptions }); }
참고로 이 코드는 CodeDomProvider.CreateProvider(language)
의 구현을 Reflector로 분석한 다음 살짝 고친 것이다.