Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving away from primary constructors

The C# 6 preview for Visual Studio 2013 supported a primary constructors feature that the team has decided will not make it into the final release. Unfortunately, my team implemented over 200 classes using primary constructors.

We're now looking for the most straightforward path to migrate our source. Since this is a one time thing, a magical regex replacement string or hacky parser would work.

Before I spend a lot of time writing such a beast, is there anyone out there that's already done this or knows of a better way?

like image 788
David Pfeffer Avatar asked Dec 12 '14 18:12

David Pfeffer


2 Answers

As I suggested in comments, you could use the version of Roslyn which does know about primary constructors to parse the code into a syntax tree, then modify that syntax tree to use a "normal" constructor instead. You'd need to put all the initializers that use primary constructor parameters into the new constructor too, mind you.

I suspect that writing that code would take me at least two or three hours, quite possibly more - whereas I could do the job manually for really quite a lot of classes in the same amount of time. Automation's great, but sometimes the quickest solution really is to do things by hand... even 200 classes may well be faster to do manually, and you could definitely parallelize the work across multiple people.

like image 90
Jon Skeet Avatar answered Nov 20 '22 15:11

Jon Skeet


(\{\s*)(\w*\s*?=\s*?\w*\s*?;\s*?)*?(public\s*\w*\s*)(\w*)(\s*?{\s*?get;\s*?\})(\s*?=\s*?\w*;\s*)
\1\2\4\5

A few answers: the first with a simple Regex find and replace which you need to repeat a few times:

  1. Regex: A few lines of explanation then the actual regex string and replacement string:

    a. In regex, first you match the full string of what your looking for (in your case a primary constructor). Not hard to do: search for curly bracket, the word public, then two words and an equals sign etc. Each text found according to this is called a Match.

    b. Sometimes there are possible repeated sequences in the text that you are looking for. (In your case: The parameters are defined in a line for each). For that, you simply mark the expected sequence as a Group by surrounding it with parenthesis.

    c. You then want to mark different parts of what you found, so you can use them or replace them in your corrected text. These parts are also called "Groups" actually "Capture Groups". Again simply surround the parts with parenthesis. In your case you'll be retaining the first captured group (the curly bracket) and the name of the property with its assignment to the parameter.

    d. Here's the regex:

    (\{\s*)(\w*\s*?=\s*?\w*\s*?;\s*?)*?(public\s*\w*\s*)(\w*)(\s*?{\s*?get;\s*?})(\s*?=\s*?\w*;\s*)
    
    1. ( 
          // ---- Capture1 -----
          {         
              // code: \{\s*? 
              // explained: curley bracket followed by possible whitespace
       ) 
    
    2. ( - Capture2  - previously corrected text 
          // - possible multiple lines of 'corrected' non-primary-constructors 
          // created during the find-replace process previously, 
    
          Propname = paramname;  // word,  equals-sign, word, semicolon 
          // code:  \w*\s*?=\s*?\w*\s*?;\s*?
          // explained:   \w - any alphanumeric, \s - any whitespace
          //              * - one or more times, *? - 0 or more times 
       )*?  
          // code: )*?
          // explained:  this group can be repeated zero or more times 
          // in other words it may not be found at all. 
          // These text lines are created during the recursive replacement process...
    
    3. ( 
          //  ----Capture 3-----
          // The first line of a primary constructor: 
          public type
          // code: public\s*\w*\s*  
          // explained: the word 'public' and then another word (and [whitespace]) 
        )
    
    4. (
        // ----- capture 4 -----
        Propname
         // code: \w@  
         // explained:  any amount of alphanumeric letters
       )
    
    5. (
         // ---- capture 5 ----
         { get; }
         // code: \s*?{\s*?get;\s*?\}
       )
    
    6. (
        // ---- capture 6 ----
         = propname; 
        code: \s*?=\s*?\w*;\s*
        explained: by now you should get it. 
    

The replacement string is

\1\2\4\6

This leaves:

{ 
    [old corrected code] 
    [new corrected line]
    possible remaining lines to be corrected. 
  1. Notepad++ 10 minutes trial-and-error. I guarantee it won't take you more than that.

  2. Visual Studio 2014 refactor. but a. You have to install it on a separate VM or PC. MS warns you not to install it side by side with your existing code. b. I'm not sure the refactor works the other way. [Here's an article about it][1]

  3. Visual Studio macros. I know I know, they're long gone, but there are at least two plugins that replace them and perhaps more. I read about them on this SO (StackOverflow) discussion. (They give a few other options) Here:

  4. Visual Commander - Free open source Visual Studio macro runner add-on
  5. VSScript - A Visual Studio add-on: costs $50 !!

  6. Try Automatic Regexp by example:You give it several examples of code in which you highlight what IS the expected result, and then the same (or other) code in which you highlight what IS NOT the expected result. You then wait for it to run through the examples and give you some regex code.

    // for the following code (from http://odetocode.com/blogs/scott/archive/2014/08/14/c-6-0-features-part-ii-primary-constructors.aspx )

    public struct Money(string currency, decimal amount) { public string Currency { get; } = currency; public decimal Amount { get; } = amount; } // I get something like: { ++\w\w[^r-u][^_]++|[^{]++(?={ \w++ =)

  7. Play with the regexp on this great site: https://www.regex101.com/

    // I first tried: \{\s*((public\s*\w*\s*)\w*(\s*?{\s*?get;\s*?})\s*?=\s*?\w*;\s*)*\}
    

The repeated sequence of the primary-constructor lines (the "repeated capture group") only captures the last one.

  1. Use c# code with regex.captures as explained here in another StackOverflow (see accepted answer)
like image 35
pashute Avatar answered Nov 20 '22 13:11

pashute