在许多属性和类中自动化Resharper的“带有支持字段的属性”?

时间:2012-07-24 16:42:58

标签: resharper

我有一些这样的C#代码(类文件= Status.cs):

    /// <summary>
    /// Constructs a status entity with the text specified
    /// </summary>
    /// <param name="someParameter">Some parameter.</param>
    public Status(string someParameter)
    {
        SomeProperty = someParameter;
    }

    /// <summary>
    /// An example of a virtual property.
    /// </summary>
    public virtual string SomeProperty { get; private set; }

我想做三件事:

  1. 执行Resharper“到具有支持字段的财产”
  2. 摆脱“私人套装”并将其替换为常规“套装”
  3. 更改构造函数,以便初始化私有字段而不是属性
  4. 所以最终结果如下:

        /// <summary>
        /// Constructs a status entity with the text specified
        /// </summary>
        /// <param name="someParameter">Some parameter.</param>
        public Status(string someParameter)
        {
            _someProperty = someParameter;
        }
    
        private string _someProperty;
    
        /// <summary>
        /// An example of a virtual property.
        /// </summary>
        public virtual string SomeProperty
        {
            get { return _someProperty; }
            set { _someProperty = value; }
        }
    

    我的问题是:有没有办法使用Resharper API自动化这种类型的重构?

    背景:

    对于那些可能想知道我为什么要这样做的人,这是因为:

    1. 我正在升级NHibernate(old = 2.1.2.4000,new = 3.3.1.4000)和Fluent NHibernate(old = 1.1.0.685,new = 1.3.0.0)。

    2. 我已经摆脱了旧的NHibernate.ByteCode.Castle.dll和配置文件中的相应行,所以我现在可以使用内置于最新NHibernate中的默认代理。

      < / LI>
    3. 在NHibernate的新实现和Fluent的新版本之间,当我尝试构建和运行单元测试时,似乎存在两个问题(部分原因是FxCop抱怨,但无论如何):

    4. a)由于“私人集”而抛出异常,并且 b)抛出异常,因为虚拟属性正在构造函数中初始化。

      所以我发现,如果我进行这些更改,它会编译并且单元测试通过。

      这对一两个类来说没问题,但是有超过800个类文件,谁知道有多少属性。

      我确信有很多方法可以做到这一点(例如使用反射,通过目录递归和解析文件等),但似乎Resharper是适合这种情况的正确工具。

      任何帮助表示感谢,谢谢,-Dave

      - 回答答案说“只需将其更改为受保护的设置即可完成”:

      不幸的是,事情并非那么简单。

      以下是运行单元测试时发生的错误(在对任何类进行任何更改之前):

      测试方法抛出异常: NHibernate.InvalidProxyTypeException:以下类型不能用作代理: .Status:方法set_StatusText应该是'public / protected virtual'或'protected internal virtual' ..Status:方法set_Location应该是'public / protected virtual'或'protected internal virtual'

      因此,如果我按照建议更改类(其中唯一的更改是将“私有集”更改为“受保护集”),则项目将无法构建,因为:

      错误2 CA2214:Microsoft.Usage:'Status.Status(string,StatusLocation)'包含一个调用链,该调用链导致调用该类定义的虚方法。检查以下调用堆栈是否存在意外后果: Status..ctor(String,StatusLocation) Status.set_Location(StatusLocation):Void C:\\ Status.cs 28

      这就是为什么还需要更改构造函数中用于初始化其中一个虚拟属性的任何语句。

      NHibernate代理(ByteCode.Castle)的先前实现似乎并不关心“私有集”,而这一点确实如此。

      而且,不可否认,第二个错误是因为FxCop,我可以在类上放置一个属性来告诉FxCop不要抱怨这个,但这似乎只是让问题消失,并初始化虚拟属性在我理解的情况下,构造函数无论如何都是不好的做法。

      所以我的问题仍然存在。我最初描述的变化是我想要做出的改变。如何自动执行这些更改?

1 个答案:

答案 0 :(得分:1)

我继续编写了一个C#实用程序来解析这样的类文件。 “受保护的集合”的想法是有效的(感谢Hazzik),但它仍然需要一个支持领域。下面的代码生成我上面描述的输出(除了使用“受保护的集合”)。问候,-Dave

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
// TODO:  write recursive algorithm to loop through directories
// TODO:  handle generic collections as Fluent NHibernate treats those differently

class Program
{
    public static string ConvertInitialCapitalToUnderscoreAndLowerCase(string input)
    {
        var firstChar = input.Substring(0, 1);
        var restOfStmt = input.Substring(1);
        var newFirst = "_" + firstChar.ToLower();
        var output = newFirst + restOfStmt;
        return output;
    }

    // this gets any tabs or spaces at the beginning of the line of code as a string,
    // so as to preserve the indentation (and/or add deeper levels of indentation)
    public static string GetCodeLineIndentation(string input)
    {
        var charArray = input.ToCharArray();

        var sbPrefix = new StringBuilder();

        foreach (var c in charArray)
        {
            // if it's a tab or a space, add it to the "prefix"
            if (c == 9 || c == ' ')
            {
                sbPrefix.Append(c);
            }
            else
            {
                // get out as soon as we hit the first ascii character (those with a value up to 127)
                break;
            }
        }

        return sbPrefix.ToString();
    }



    static void Main(string[] args)
    {

        const string path = @"C:\pathToFile\Status.cs";

        Console.WriteLine("Examining file:  " + path);

        if (!File.Exists(path))
        {
            Console.WriteLine("File does not exist:  " + path);
            throw new FileNotFoundException(path);
        }

        // Read the file.
        var arrayOfLines = File.ReadAllLines(path);

        // Convert to List<string>
        var inputFileAsListOfStrings = new List<string>(arrayOfLines);

        // See if there are any virtual properties.
        var virtualProps = inputFileAsListOfStrings.Where(s => s.Contains("public virtual")).ToList();

        // See if there are any "private set" strings.
        var privateSets = inputFileAsListOfStrings.Where(s => s.Contains("private set")).ToList();

        if (virtualProps.Count > 0)
        {
            Console.WriteLine("Found " + virtualProps.Count + " virtual properties in the class...");
        }

        if (privateSets.Count > 0)
        {
            Console.WriteLine("Found " + privateSets.Count + " private set statements in the class...");
        }

        // Get a list of names of the virtual properties
        // (the 4th "word", i.e. index = 3, in the string, will be the property name, 
        // e.g. "public virtual string SomePropertyName"
        var virtualPropNames = virtualProps.Select(vp => vp.Trim().Split(' ')).Select(words => words[3]).ToList();

        if (virtualPropNames.Count() != virtualProps.Count())
        {
            throw new Exception("Error:  the list of virtual property names does not equal the number of virtual property statements!");
        }

        // Find all instances of the virtual properties being initialized.

        // By examining the overall file for instances of the virtual property name followed by an equal sign,
        // we can identify those lines which are statements initializing the virtual property.
        var initializeStatements = (from vpName in virtualPropNames
                                    from stmt in inputFileAsListOfStrings
                                    let stmtNoSpaces = stmt.Trim().Replace(" ", "")
                                    where stmtNoSpaces.StartsWith(vpName + "=")
                                    select stmt).ToList();

        if (initializeStatements.Count() > 0)
        {
            Console.WriteLine("Found " + initializeStatements.Count + " initialize statements in the class...");
        }

        // now process the input based on the found strings and write the output
        var outputFileAsListOfStrings = new List<string>();

        foreach (var inputLineBeingProcessed in inputFileAsListOfStrings)
        {
            // is the input line one of the initialize statements identified previously?
            // if so, rewrite the line.

            // e.g.  
            // old line:  StatusText = statusText;  
            // becomes:  _statusText = statusText;

            var isInitStmt = false;
            foreach (var initStmt in initializeStatements)
            {
                if (inputLineBeingProcessed != initStmt) continue;

                // we've found our statement; it is an initialize statement;
                // now rewrite the format of the line as desired
                var prefix = GetCodeLineIndentation(inputLineBeingProcessed);

                var tabAndSpaceArray = new[] {' ', '\t'};

                var inputLineWithoutPrefix = inputLineBeingProcessed.TrimStart(tabAndSpaceArray);

                var outputLine = prefix + ConvertInitialCapitalToUnderscoreAndLowerCase(inputLineWithoutPrefix);

                // write the line (preceded by its prefix) to the output file
                outputFileAsListOfStrings.Add(outputLine);

                Console.WriteLine("Rewrote INPUT: " + initStmt + " to OUTPUT:  " + outputLine);
                isInitStmt = true;

                // we have now processed the input line; no need to loop through the initialize statements any further
                break;
            }

            // if we've already determined the current input line is an initialize statement, no need to proceed further;
            // go on to the next input line
            if (isInitStmt)
                continue;


            // is the input line one of the "public virtual SomeType SomePropertyName" statements identified previously?
            // if so, rewrite the single line as multiple lines of output.

            // the input will look like this:

            /*

            public virtual SomeType SomePropertyName { get; set; }

            */

            // first, we'll need a private variable which corresponds to the original statement in terms of name and type.

            // what we'll do is, write the private field AFTER the public property, so as not to interfere with the XML
            // comments above the "public virtual" statement.

            // the output will be SIX LINES, as follows:

            /*


            public virtual SomeType SomePropertyName
            {
                get { return _somePropertyName; }
                protected set { _someProperty = value; }
            }
            private SomeType _somePropertyName;



            */

            var isPublicVirtualStatement = false;

            foreach (var vp in virtualProps)
            {
                if (inputLineBeingProcessed != vp) continue;

                // the input line being processed is a "public virtual" statement;
                // convert it into the six line output format
                var thisOutputList = new List<string>();

                // first separate any indentation "prefix" that may exist (i.e. tabs and/or spaces),
                // from the actual string of text representing the line of code
                var prefix = GetCodeLineIndentation(inputLineBeingProcessed);

                var tabAndSpaceArray = new[] { ' ', '\t' };

                var inputLineWithoutPrefix = inputLineBeingProcessed.TrimStart(tabAndSpaceArray);

                var originalVpStmt = inputLineWithoutPrefix.Split(' ');

                // first output line (preceded by its original prefix)
                var firstOutputLine =   prefix + 
                                        originalVpStmt[0] + ' ' + 
                                        originalVpStmt[1] + ' ' + 
                                        originalVpStmt[2] + ' ' +
                                        originalVpStmt[3];
                thisOutputList.Add(firstOutputLine);

                // second output line (indented to the same level as the original prefix)
                thisOutputList.Add(prefix + "{");

                // get field name from property name
                var fieldName = ConvertInitialCapitalToUnderscoreAndLowerCase(originalVpStmt[3]);

                // third output line (indented with the prefix, plus one more tab)
                var thirdOutputLine = prefix + "\t" + "get { return " + fieldName + "; }";
                thisOutputList.Add(thirdOutputLine);

                // fourth output line (indented with the prefix, plus one more tab)
                var fourthOutputLine = prefix + "\t" + "protected set { " + fieldName + " = value; }";
                thisOutputList.Add(fourthOutputLine);

                // fifth output line (indented to the same level as the first curly bracket)
                thisOutputList.Add(prefix + "}");

                // sixth output line (the "index 2" value of the original statement will be the string representing the .Net type)
                // (indentation is the same as the "public virtual" statement above)

                var sixthOutputLine =   prefix + 
                                        "private" + ' ' +
                                        originalVpStmt[2] + ' ' +
                                        fieldName + ";";
                thisOutputList.Add(sixthOutputLine);

                // now write the six new lines to the master output list
                outputFileAsListOfStrings.AddRange(thisOutputList);

                isPublicVirtualStatement = true;
                Console.WriteLine("Rewrote INPUT: " + inputLineBeingProcessed + " to OUTPUT:  <multi-line block>");
                break;

            }

            // if we've already determined the current input line is a "public virtual" statement, no need to proceed further;
            // go on to the next input line
            if (isPublicVirtualStatement)
                continue;


            // if we've gotten this far, the input statement is neither an "initialize" statement, nor a "public virtual" statement;
            // So just write the output.  Don't bother logging this as most lines will not be ones we'll process.
            outputFileAsListOfStrings.Add(inputLineBeingProcessed);

        }

        // write the output file
        var newPath = path.Replace(".cs", "-NEW.cs");
        File.WriteAllLines(newPath, outputFileAsListOfStrings);

    }
}