성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Roslyn을 이용해 C# 문법 변형하기 (2)</h1> <p> Pre build 단계를 거치는 것도 좋겠지만, 역시나 geek스러운 사람들이 원하는 것은 컴파일 시 변형입니다. 이에 대해 좋은 사례를 아래의 블로그에서 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Adventures in Roslyn: Adding crazily powerful operator overloading to C# 6 ; <a target='tab' href='https://smellegantcode.wordpress.com/2014/04/24/adventures-in-roslyn-adding-crazily-powerful-operator-overloading-to-c-6/'>https://smellegantcode.wordpress.com/2014/04/24/adventures-in-roslyn-adding-crazily-powerful-operator-overloading-to-c-6/</a> </pre> <br /> 위의 글을 정리해 보면.<br /> <br /> 원래 다음과 같이 배열을 연결하는 '+' 연산 기능은 C#에 없는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Program { static void Main(string[] args) { var nums1 = new[] { 1, 2, 3 }; var nums2 = new[] { 4, 5 }; var nums3 = <span style='color: blue; font-weight: bold'>nums1 + nums2</span>; } } </pre> <br /> '+' 연산자가 사용된 피연산자에 대해 '확장 메서드'를 정의해 둔다면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Collections.Generic; using System.Linq; public static class EnumerableOperatorExtension { <span style='color: blue; font-weight: bold'>public static IEnumerable<T> Addition<T>(this IEnumerable<T> left, IEnumerable<T> right)</span> { return left.Concat(right); } } class Program { static void Main(string[] args) { var nums1 = new[] { 1, 2, 3 }; var nums2 = new[] { 4, 5 }; var nums3 = <span style='color: blue; font-weight: bold'>nums1 + nums2</span>; } } </pre> <br /> 어차피 '+' 연산자의 메서드 명이 "Addition"이기 때문에 그것과 연결지어 컴파일을 가능하게 만들자는 의도입니다. 이를 위해 수정한 곳은, Compilers / CSharp / CSharpCodeAnalysis 프로젝트의 Binder_Operators.cs 파일입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // E:\roslyn\src\Compilers\CSharp\Portable\Binder\Binder_Operators.cs private BoundExpression BindSimpleBinaryOperator(BinaryExpressionSyntax node, DiagnosticBag diagnostics, BoundExpression left, BoundExpression right, ref int compoundStringLength) { // ...[생략]... LookupResultKind resultKind; ImmutableArray<MethodSymbol> originalUserDefinedOperators; <span style='color: blue; font-weight: bold'>var best = this.BinaryOperatorOverloadResolution(kind, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);</span> // However, as an implementation detail, we never "fail to find an applicable // operator" during overload resolution if we have x == null, etc. We always // find at least the reference conversion object == object; the overload resolution // code does not reject that. Therefore what we should do is only bind // "x == null" as a nullable-to-null comparison if overload resolution chooses // the reference conversion. BoundExpression resultLeft = left; BoundExpression resultRight = right; MethodSymbol resultMethod = null; ConstantValue resultConstant = null; BinaryOperatorKind resultOperatorKind; TypeSymbol resultType; bool hasErrors; <span style='color: blue; font-weight: bold'>if (!best.HasValue)</span> { resultOperatorKind = kind; resultType = CreateErrorType(); hasErrors = true; } else { // ...[생략]... } // ...[생략]... } </pre> <br /> BinaryOperatorOverloadResolution 메서드에서 '+' 연산자에 대해 overload된 연산자를 못 찾게 되어 실패를 반환하기 때문에 if (!best.HasValue) 절에서 한번 더 다음과 같이 확장 메서드에서 찾아 보는 코드를 추가합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if (!best.HasValue) { string methodName = Enum.GetName(typeof(BinaryOperatorKind), kind); var methodCall = <span style='color: blue; font-weight: bold'>MakeInvocationExpression</span>(node, left, methodName, ImmutableArray.Create(right), diagnostics); if (methodCall != null && !methodCall.HasAnyErrors) { <span style='color: blue; font-weight: bold'>return methodCall;</span> } resultOperatorKind = kind; resultType = CreateErrorType(); hasErrors = true; } </pre> <br /> 사실, 저는 이 예제에서 '문법 확장'의 흥미로움보다는 이런 변화가 Visual Studio의 에디터에서도 일관되게 적용된다는 점이 마음에 듭니다. "<a target='tab' href='https://smellegantcode.wordpress.com/2014/04/24/adventures-in-roslyn-adding-crazily-powerful-operator-overloading-to-c-6/'>Adventures in Roslyn: Adding crazily powerful operator overloading to C# 6</a>" 글에서도 설명하고 있지만 Addition 확장 메서드가 없는 경우 다음과 같이 Visual Studio 코드 에디터에서 올바르지 않다는 적색선(red squiggle)이 보이지만,<br /> <br /> <img alt='roslyn1.jpg' src='/SysWebRes/bbs/roslyn1.jpg' /><br /> <br /> Addition 확장 메서드를 추가하면 이렇게 사라집니다.<br /> <br /> <img alt='roslyn2.jpg' src='/SysWebRes/bbs/roslyn2.jpg' /><br /> <br /> 그런데, 직접 제가 현재 버전의 RoslynLight.sln 빌드로 해보니까 아쉽지만 다음과 같이 적색선이 보입니다. (물론, 컴파일은 잘 됩니다.)<br /> <br /> <img alt='roslyn3.png' src='/SysWebRes/bbs/roslyn3.png' /><br /> <br /> 이유를 대충 분석해 보면, Process Explorer를 통해 devenv.exe내에 로드된 "Microsoft.CodeAnalysis.CSharp.dll"의 경로가 원래의 Visual Studio가 쓰던 것이기 때문입니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\Windows\assembly\NativeImages_v4.0.30319_32\Microsoft.C1d3c2215#\a1f942b3d6393e8b6c1d6a89f416f941\Microsoft.CodeAnalysis.CSharp.ni.dll </pre> <br /> 즉, GAC가 아닌 "%LOCALAPPDATA%\Microsoft\VisualStudio\14.0Roslyn\Extensions\MSOpenTech\OpenSourceDebug\0.7" 폴더에 우리가 새롭게 빌드한 "Microsoft.CodeAnalysis.CSharp.dll" 어셈블리를 로드해야만 적색선이 안 보일텐데, 그렇지 않은 것입니다.<br /> <br /> 그렇긴 해도 과거의 어느 시점에는 저것이 통합되었다는 것을 의미하니... 아마도 정식 버전에서는 어떻게 바뀌는 지 두고 봐야 할 것 같습니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> "<a target='tab' href='https://smellegantcode.wordpress.com/2014/04/24/adventures-in-roslyn-adding-crazily-powerful-operator-overloading-to-c-6/'>Adventures in Roslyn: Adding crazily powerful operator overloading to C# 6</a>" 글에서 시도한 것은 어찌 보면 C# 6.0의 새로운 기능으로 추가된,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > New Language Features in C# 6 ; <a target='tab' href='https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6'>https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6</a> </pre> <br /> "Extension Add methods in collection initializers"라는 것과 다소 유사한 원칙을 따릅니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 외에도 "Daniel Earwicker" 씨가 설명한 문법 변형 관련된 글이 2개가 더 있으니 관심있으신 분은 참고하세요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Adventures in Roslyn: A new kind of managed lvalue pointer ; <a target='tab' href='https://smellegantcode.wordpress.com/2014/04/27/adventures-in-roslyn-a-new-kind-of-managed-lvalue-pointer/'>https://smellegantcode.wordpress.com/2014/04/27/adventures-in-roslyn-a-new-kind-of-managed-lvalue-pointer/</a> Adventures in Roslyn: Using pointer syntax as a shorthand for IEnumerable ; <a target='tab' href='https://smellegantcode.wordpress.com/2014/04/26/adventures-in-roslyn-using-pointer-syntax-as-a-shorthand-for-ienumerable/'>https://smellegantcode.wordpress.com/2014/04/26/adventures-in-roslyn-using-pointer-syntax-as-a-shorthand-for-ienumerable/</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그나저나, 이런 시도도 있었군요. ^^<br /> <br /> <a name='fody'></a> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Fody/Fody ; <a target='tab' href='https://github.com/Fody/Fody'>https://github.com/Fody/Fody</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Manipulating the IL of an assembly as part of a build requires a significant amount of plumbing code. This plumbing code involves knowledge of both the MSBuild and Visual Studio APIs. Fody attempts to eliminate that plumbing code through an extensible add-in model. <br /> </div><br /> <br /> <br /> <hr style='width: 50%' /><br /> <br /> 로슬린 소스 코드 중에는 자동 생성되는 코드들이 있으므로 해당 파일을 직접 수정하는 것에 유의해야 합니다. 가령 이름에 ".Generated."라고 붙은 것들이 있는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > E:\roslyn\Binaries\Obj\CSharpCodeAnalysis\Debug\Syntax.xml.Generated.cs </pre> <br /> 이런 경우 "E:\roslyn\src\Compilers\CSharp\Portable\Syntax\Syntax.xml" 파일로부터 자동 생성되는 것들이기 때문에 변경하고 싶다면 syntax.xml을 변경해야 합니다.<br /> <br /> 그럼, 어떻게 자동생성되는지 과정을 한번 찾아 볼까요? ^^<br /> <br /> 우선 Syntax.xml 파일을 담고 있는 프로젝트 파일의 내용을 보면 다음과 같이 VSL.Imports.targets 파일과 CSharpSyntaxGeneratorToolPath 항목을 정의하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ============== E:\roslyn\src\Compilers\CSharp\Portable\CSharpCodeAnalysis.csproj ============== <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. --> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- ...[생략]... --> <span style='color: blue; font-weight: bold'><CSharpSyntaxGeneratorToolPath>$(OutDir)CSharpSyntaxGenerator.exe</CSharpSyntaxGeneratorToolPath></span> <!-- ...[생략]... --> <ImportGroup Label="Targets"> <span style='color: blue; font-weight: bold'><Import Project="..\..\..\Tools\Microsoft.CodeAnalysis.Toolset.Open\Targets\VSL.Imports.targets" /></span> <Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> </ImportGroup> </Project> </pre> <br /> VSL.Imports.targets 파일을 보면 이것은 다시 내부에 GenerateCompilerInternals.targets을 포함합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ============== E:\roslyn\src\Tools\Microsoft.CodeAnalysis.Toolset.Open\Targets\VSL.Imports.targets ============== <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- ...[생략]... --> <span style='color: blue; font-weight: bold'><Import Project="GenerateCompilerInternals.targets" /></span> <!-- ...[생략]... --> </Project> </pre> <br /> GenerateCompilerInternals.targets을 보면 이제 수수께끼가 풀립니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ============== E:\roslyn\src\Tools\Microsoft.CodeAnalysis.Toolset.Open\Targets\GenerateCompilerInternals.targets ============== <!-- ...[생략]... --> <Target Name="GenerateSyntaxModel" Inputs="@(SyntaxDefinition);$(VBSyntaxGeneratorToolPath);$(CSharpSyntaxGeneratorToolPath)" Outputs="@(SyntaxDefinition -> '$(IntermediateOutputPath)%(Filename)%(Extension).Generated$(DefaultLanguageSourceExtension)')" Condition="'$(Language)' == 'VB' or '$(Language)' == 'C#'" > <PropertyGroup> <SyntaxGenerator Condition="'$(Language)' == 'VB'">$(MonoPrefix) "$(VBSyntaxGeneratorToolPath)"</SyntaxGenerator> <span style='color: blue; font-weight: bold'><SyntaxGenerator Condition="'$(Language)' == 'C#'">$(MonoPrefix) "$(CSharpSyntaxGeneratorToolPath)"</SyntaxGenerator></span> <GeneratedSyntaxModel>@(SyntaxDefinition -> '$(IntermediateOutputPath)%(Filename)%(Extension).Generated$(DefaultLanguageSourceExtension)')</GeneratedSyntaxModel> </PropertyGroup> <span style='color: blue; font-weight: bold'><Exec Command='$(SyntaxGenerator) @(SyntaxDefinition) $(GeneratedSyntaxModel)' Outputs="$(GeneratedSyntaxModel)" > <Output TaskParameter="Outputs" ItemName="FileWrites" /> </Exec></span> </Target> <!-- ...[생략]... --> </pre> <br /> 테스트를 위해 위의 <Exec /> 부분을 주석처리하면 Syntax.xml 파일을 변경해도 Syntax.xml.Generated.cs 파일의 내용이 변경되지 않습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1094
(왼쪽의 숫자를 입력해야 합니다.)