RoslynによるCの拡匵。 安党な通話

あなたが珟圚プログラミングしおいるX蚀語では䜕かが足りないず感じたこずはありたすか あなたの人生を絶察に幞せにしないかもしれないが、間違いなく倚くの楜しい瞬間を远加するかもしれない小さな、しかし楜しいパン。 そしお、黒のねたみで、あなたはこのこずのあるY蚀語を芋お、悲しいこずにため息を぀いお、倜にあなたの最愛の枕に無力の涙をひそかに泚ぎたす。 起こった



おそらくCは、動的に開発し、生掻を簡玠化する機胜を远加しおいるため、倚くの人ず比べお、そのようなvy望の理由を支持者に䞎えたせん。 それでも、完璧には限界がありたせん。そしお、私たち䞀人䞀人に-圌自身のものです。



私はこの仕事で私にずっお優先事項がロズリンの歯を詊しおみたいずいう欲求であり、埌で説明するアむデア自䜓がこのラむブラリをテストするための機䌚ずテスト䟋であったこずにすぐに泚意したす。 しかし、研究ず実装の過皋で、䞀郚のタンバリンでは実際に蚀語構文を拡匵するために実際に結果を䜿甚できるこずがわかりたした。 これを行う方法に぀いおは、最埌に簡単に説明したす。 それたでの間、始めたしょう。



セヌフコヌルず倚分モナド



安党な呌び出しのアむデアは、null䞊のクラスの迷惑なチェックを取り陀くこずです。これは、必芁であるず同時に、コヌドを著しく詰たらせ、読みやすさを損ないたす。 同時に、NullReferenceExceptionの絶え間ない脅嚁にさらされるこずは望みたせん。



この問題は、 Maybeモナドを䜿甚しお関数型プログラミング蚀語で解決さ​​れたした。その本質は、ボクシング埌、パむプラむンコンピュヌティングで䜿甚される型に倀たたはNothing倀が含たれるこずがあるこずです。 パむプラむンの前の蚈算で䜕らかの結果が埗られた堎合、次の蚈算が実行され、Nothingが返された堎合、次の蚈算の代わりにNothingが返されたす。



Cでは、このモナドの実装のすべおの条件が䜜成されたす-Nothingの代わりにnullが䜿甚され、それらのNullable <T>バヌゞョンは構造型に䜿甚できたす。 原則ずしお、このアむデアはすでに空䞭にあり、LINQを䜿甚しおこのモナドをCで実装した蚘事がいく぀かありたした。 それらの1぀はDmitry Nesteruk mezastelに属しおいたす 、 別のものがありたす。



しかし、このようなアプロヌチのすべおの誘惑で、盎接呌び出しの代わりにラムダ関数ずLINQからのラッパヌを䜿甚する必芁があるため、モナドを䜿甚した結果のコヌドは非垞にがやけおいるこずに泚意する必芁がありたす。 ただし、蚀語の構文手段がないず、より゚レガントに実装するこずはほずんど䞍可胜です。



私の考えおいるように、このアむデアを実装するかなり゚レガントな方法は、私の愛するJetBrains Null-safety 䌚瀟の人によっおただ䜜成されおいないJDKのKotlin蚀語の仕様で芋぀けたした。 刀明したように、これはすでにGroovyで、おそらくどこか別の堎所にありたす。



それで、この安党な呌び出しステヌトメントは䜕ですか 匏があるずしたす

string text = SomeObject.ToString();
      
      





SomeObjectがnullの堎合、既に説明したように、NullReferenceExceptionを取埗するこずは避けられたせん。 これを避けるために、盎接呌び出し挔算子 '。'に加えお定矩したす。 安党な呌び出し挔算子「」 次のずおりです。

 string text = SomeObject?.ToString();
      
      





そしお実際には匏です

 string text = SomeObject != null ? SomeObject.ToString() : null;
      
      





安党に呌び出されたメ゜ッドたたはプロパティが構造型を返す堎合、割り圓おられた倉数はNullable型である必芁がありたす。

 int? count = SomeList?.Count;
      
      





通垞の呌び出しず同様に、これらの安党な呌び出しはチェヌンで䜿甚できたす。次に䟋を瀺したす。

 int? length = SomeObject?.ToString()?.Length;
      
      





匏に倉換したす

 int? length = SomeObject != null ? SomeObject.ToString() != null ? SomeObject.ToString().Length : null : null;
      
      





これにより、远加の関数呌び出しが生成されるため、私が提案しおいる倉換の欠点のいく぀かが隠されおいたす。 実際、たずえば次の圢匏に倉換するこずが望たしいでしょう。

 var temp = SomeObject; string text = null; if (temp != null) text = temp.ToString();
      
      





ただし、Roslynのやや冗長な性質により、䟋が肥倧化し、退屈にならないように、倉換をより単玔にするこずにしたした。 ただし、これは次の郚分にありたす。



プロゞェクトロズリン



ご存知かもしれたせんが、 Roslynプロゞェクトの CTPバヌゞョンが最近リリヌスされたした。CおよびVB開発者は、マネヌゞコヌドを䜿甚しお蚀語コンパむラを完党に曞き換え、これらのコンパむラぞのアクセスはAPIずしお公開されたした。 これにより、開発者は倚くの䟿利なこずができたす。たずえば、非垞に䟿利で簡単な分析、最適化、コヌドの生成、スタゞオ、および堎合によっおは独自のDSLの拡匵機胜ずコヌド修正の蚘述が可胜です。 確かに、すぐにリリヌスされるこずはなく、Visual Studioの1぀のバヌゞョンを介しお既にリリヌスされたすが、今それを感じたいです。



問題の解決に移りたしょう。たず、この蚀語の拡匵機胜が実際にどのように䜿甚されおいるかを想像しおみおください。 明らかにい぀ものように、お気に入りのIDEでコヌドを蚘述し、必芁に応じお安党な呌び出し挔算子を䜿甚し、コンパむル䞭にProjectを抌したす。ProjectRoslynを䜿甚しお䜜成したプログラムは、これらすべおを構文的に正しいCコヌドに倉換し、すべおがコンパむルされたした。 倱望させおいただきたすが、Roslynでは、珟圚のcsc.exeコンパむラの動䜜を劚害するこずはできたせん。 同じvNextスタゞオで、コンパむラがそのマネヌゞカりンタヌパヌトに眮き換えられるず、そのような機䌚が珟れる可胜性がありたす。 しかし、圌女がいなくなっおいる間。



同時に、すでに2぀の回避策がありたす。

  1. 同じRoslyn APIを䜿甚しお、珟圚のcsc.exeの代わりに独自のコンパむラを䜜成し、csc.exeを独自のアナログに眮き換えおビルドシステムを倉曎するこずができたす。コヌド。
  2. コン゜ヌルプログラムを゜ヌスコヌドファむルを倉換し、受け取った新しい゜ヌスをObjフォルダヌに保存するビルド前タスクずしお䜿甚できたす。 ビルド前フェヌズのxamlファむルが.g.csファむルに倉換されるずき、WPFは珟圚非垞によく䌌た方法でコンパむルされおいたす。




Project Roslynはいく぀かのタむプの機胜を提䟛したすが、重芁な機胜の1぀は、抜象構文ツリヌの構築、分析、および倉換です。 さらに䜿甚するのは、この機胜です。



実装



もちろん、以䞋に曞かれおいるものはすべお単なる䟋であり、倚くの欠陥に悩たされおおり、倧幅な改善がなければ実際に䜿甚するこずはできたせんが、そのようなこずが原則的に行われるこずを瀺しおいたす。

実装に移りたしょう。 プログラムを䜜成するには、最初にRoslyn SDKをむンストヌルする必芁がありたす。これは、 リンクからダりンロヌドでき、Visual Studio 2010甚のService Pack 1ずVisual Studio 2010 SDK SP1も最初にむンストヌルする必芁がありたす。

これらすべおの操䜜の埌、Roslynサブ項目が新しいプロゞェクトを䜜成するためのメニュヌに衚瀺されたす。これには、いく぀かのプロゞェクトテンプレヌトが含たれおいたすその䞀郚はIDEに統合できたす。 簡単なコン゜ヌルアプリケヌションを䜜成したす。

たずえば、次の「゜ヌスコヌド」を䜿甚したす。

 public class Example { public const string CODE = @"using System; using System.Linq; using System.Windows; namespace HelloWorld { public class TestClass { public string TestField; public string TestProperty { get; set; } public string TestMethod() { return null; } public string TestMethod2(int k, string p) { return null; } public TestClass ChainTest; } public class OtherClass { public void Test() { TestClass test; string testStr1; testStr1 = test?.TestField; string testStr3 = test?.TestProperty; string testStr4 = test?.TestMethod(); string testStr5 = test?.TestMethod2(100, testStr3); var test3 = test?.ChainTest?.TestField; } } }"; }
      
      





この゜ヌスコヌドは、安党な呌び出しステヌトメントを陀き、構文的に正しいだけでなく、コンパむルされたすが、これは倉換には必芁ありたせん。



たず、゜ヌスファむルから抜象構文ツリヌを構築する必芁がありたす。 これは2぀の方法で行われたす。

 SyntaxTree tree = SyntaxTree.ParseCompilationUnit(Example.CODE); SyntaxNode root = tree.Root;
      
      





構文ツリヌはSyntaxTreeクラスによっお定矩され、奇劙なこずに、ベヌスSyntaxNodeタむプから継承するノヌドのツリヌであり、各ノヌドは特定の匏バむナリ匏、条件匏、メ゜ッド呌び出し匏、プロパティ定矩、倉数を衚したす。 圓然、SyntaxNodeの䞋䜍クラスのむンスタンスによっお、絶察にC構造を衚瀺できたす。 さらに、SyntaxTreeクラスには、キヌワヌド、リテラル、識別子、句読点䞭括匧、括匧、コンマ、セミコロンの最小構文ブロックのレベルで解析する゜ヌスコヌドを定矩するSyntaxTokenセットが含たれおいたす。 最埌に、SyntaxTree inにはSyntaxTrivia芁玠コヌドを理解する䞊で重芁ではない芁玠が含たれたす。スペヌスずタブ、コメント、プリプロセッサディレクティブなどです。



ここで、1぀の小さな詳现を知っおおく必芁がありたす-Roslynはファむルの解析に非垞に寛容です。 ぀たり、良い方法ではありたすが、分析のために構文的に正しい゜ヌスコヌドを提䟛する必芁がありたす。実際、䜕らかの方法で䜕らかのテキストを絶察に䜕らかのASTに倉換しようずしたす。 構文的に正しくないコヌドを含めたす。 この事実を䜿甚したす。 構文朚を構築しお、Roslynが安党な呌び出し挔算子を朚に衚瀺する方法を調べおみたしょう。



Roslynの芳点から芋るず、テストはすべおテストでしたか.TestFieldは、条件-「test」、匏「when true」-「.TestField」、および空の匏「when wrong」を持぀䞉項挔算子です。 この情報を歊噚に、ツリヌを倉換したす。 ここで、Roslynの別の機胜に遭遇したす-構築する構文ツリヌは䞍倉です。぀たり、既存の構造を盎接修正するこずはできたせん。 しかし、それは問題ではありたせん。 Roslynは、そのような操䜜にSyntaxRewriterクラスを䜿甚するこずをお勧めしたす。これは、SyntaxVisitorクラスを継承したす。SyntaxVisitorクラスは、名のずおり、悪名高いVisitorパタヌンを実装したす。 特定の各タむプのノヌドぞの蚪問を凊理する倚くの仮想メ゜ッドが含たれおいたすVisitFieldDeclaration、VisitEnumMemberDeclarationなど、合蚈で玄180個ありたす。



子孫のSyntaxRewriterクラスを䜜成し、VisitorConditionalExpressionメ゜ッドをオヌバヌラむドする必芁がありたす。このメ゜ッドは、蚪問者が䞉項挔算子である匏をバむパスするずきに呌び出されたす。 次に、特に小さいため、実装コヌド党䜓を提䟛し、いく぀かの説明のみを远加したす。

 //              public class SafeCallRewriter : SyntaxRewriter { //          ?. public bool IsSafeCallRewrited { get; set; } protected override SyntaxNode VisitConditionalExpression(ConditionalExpressionSyntax node) { if (IsSafeCallExpression(node)) { // expression  ,   null string identTxt = node.Condition.GetText(); ExpressionSyntax ident = Syntax.ParseExpression(identTxt); // expression  ,      != null string exprTxt = node.WhenTrue.GetText(); exprTxt = exprTxt.Substring(1, exprTxt.Length - 1);//     exprTxt = identTxt + '.' + exprTxt; ExpressionSyntax expr = Syntax.ParseExpression(exprTxt); ExpressionSyntax synt = Syntax.ConditionalExpression(//  condition: Syntax.BinaryExpression(//  ident != null SyntaxKind.NotEqualsExpression, left: ident, //  -   right: Syntax.LiteralExpression(SyntaxKind.NullLiteralExpression)), // null whenTrue: expr, whenFalse: Syntax.LiteralExpression(SyntaxKind.NullLiteralExpression)); IsSafeCallRewrited = true; return synt; } return base.VisitConditionalExpression(node); } //          private bool IsSafeCallExpression(ConditionalExpressionSyntax node) { return node.WhenTrue.GetText()[0] == '.'; } }
      
      





私の最初の実装はASTの論理構造でのみ動䜜しようずし、匏のテキスト衚珟では動䜜を軜芖しようずしたしたが、その耇雑さはすぐに考えられるすべおの制限を超え始めたした。 安党な呌び出しずそのタむプを定矩するための関数は、フィヌルドずプロパティ、メ゜ッドの呌び出し、安党な呌び出しのチェヌンの3぀だけでした。これらはすべお、SyntaxNodeクラスの異なる継承者であるず思われたためです。 完党に吐き出した埌、私は最初のオプションをゎミ箱に捚お、2回目はRoslynが提䟛する䟿利なGetTextおよびParseExpression関数ず、行レベルでのいく぀かの汚いハックを䜿甚したした:)。



たた、構文ノヌドこの堎合はConditionalExpressionの䜜成プロセスず、この堎合の名前付きパラメヌタヌのようなCチップの䜿甚の快適性にも泚意するこずをお勧めしたす。 そうでない堎合、構文ノヌドを構築するプロセスで、倢䞭になる可胜性がありたす。



メむンプロシヌゞャのコヌドは次のずおりです。

 static void Main(string[] args) { //   SyntaxTree tree = SyntaxTree.ParseCompilationUnit(Example.CODE); SyntaxNode root = tree.Root; SafeCallRewriter rewriter = new SafeCallRewriter(); do { rewriter.IsSafeCallRewrited = false; // ,           root = rewriter.Visit(root); } while (rewriter.IsSafeCallRewrited);//        1 maybe- root = root.Format();// Ctrl+K, Ctrl+D Console.WriteLine(root.ToString()); }
      
      





コヌルチェヌンを凊理するには、ツリヌのいく぀かの曞き換えが必芁であるこずを説明したす。 もちろん、これは再垰によっお行うこずもできたすが、おそらくこの堎合、コヌドががやけるだけです。 たた、Formatの玠晎らしい機胜にも泚意しおください。 指定されたスタむルのコヌドの曞匏蚭定をプログラムで行いたす。 必芁なすべおのSyntaxTriviaをASTに远加したす。



結果ずしお、次のコヌドがありたす。

 using System; using System.Linq; using System.Windows; namespace HelloWorld { public class TestClass { public string TestField; public string TestProperty { get; set; } public string TestMethod() { return null; } public string TestMethod2(int k, string p) { return null; } public TestClass ChainTest; } public class OtherClass { public void Test() { TestClass test; string testStr1; testStr1 = test != null ? test.TestField : null; string testStr3 = test != null ? test.TestProperty : null; string testStr4 = test != null ? test.TestMethod() : null; string testStr5 = test != null ? test.TestMethod2(100, testStr3) : null; var test3 = test != null ? test.ChainTest != null ? test.ChainTest.TestField : null : null; } } }
      
      





そのため、Roslynずの最初の知り合いは成功し、蚀語拡匵機胜を䜜成するこずは必ずしも必芁ではありたせんが、䞀般的にその芋通しは非垞に良奜です。 おそらく、愛奜家がいる堎合、これはより深く、より真剣に行うこずができたす。 Cには、ただ足りないものがたくさんありたす。 :)



PS Roslynのこのような䜿甚のもう1぀の䟋は、私を倧いに助けおくれたした。



All Articles