3

次のような 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; }

そして、私はそれに3つのことをしたい:

  1. その上で「バッキングフィールドを持つプロパティへ」Resharperを実行します
  2. 「プライベート セット」を取り除き、通常の「セット」に置き換えます
  3. プロパティの代わりにプライベート フィールドを初期化するようにコンストラクタを変更します。

したがって、最終結果は次のようになります。

    /// <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 に組み込まれている既定のプロキシを使用できるようになりました。

  3. NHibernate の新しい実装と Fluent の新しいバージョンの間には、単体テストをビルドして実行しようとすると、2 つの問題があるようです (これの一部は FxCop の不満ですが、とにかく):

a) 「プライベート セット」が原因で例外がスローされ、b) コンストラクターで仮想プロパティが初期化されているために例外がスローされます。

そのため、これらの変更を行うと、コンパイルされ、単体テストに合格することがわかりました。

1 つまたは 2 つのクラスについては問題ありませんが、800 を超えるクラス ファイルがあり、プロパティの数は誰にもわかりません。

これを行う方法はたくさんあると思います (リフレクションの使用、ディレクトリの再帰、ファイルの解析など) が、Resharper はこのようなものに適したツールのようです。

助けていただければ幸いです, ありがとう, -デイブ

--「保護されたセットに変更するだけで完了です」という回答への応答:

残念ながら、それはそれほど単純ではありません。

単体テストの実行時に発生するエラーは次のとおりです (クラスに変更を加える前)。

テスト メソッドが例外をスローしました: NHibernate.InvalidProxyTypeException: 次の型はプロキシとして使用できません: .Status: メソッド set_StatusText は '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

そのため、これらの仮想プロパティの 1 つを初期化するコンストラクター内のステートメントも変更する必要があります。

NHibernate プロキシ (ByteCode.Castle) の以前の実装は、「プライベート セット」を気にしていないようでしたが、これは気にします。

そして、確かに、2 番目のエラーは FxCop が原因であり、クラスに属性を配置して、FxCop にこれについて文句を言わないように指示することもできますが、それは問題を解決するだけのようであり、コンストラクターで仮想プロパティを初期化することは、私が理解しているように、とにかく悪い習慣です。

だから私の質問はまだ立っています。最初に説明した変更は、私が行いたい変更です。これらの変更を自動化するにはどうすればよいですか?

4

1 に答える 1

1

私は先に進み、そのようなクラス ファイルを解析するための C# ユーティリティを作成しました。「保護されたセット」のアイデアはある程度有効ですが (Hazzik に感謝)、まだバッキング フィールドが必要です。以下のコードは、上記で説明した出力を生成します (「保護されたセット」を使用する場合を除く)。よろしく、 -デイブ

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);

    }
}
于 2012-07-25T14:56:23.147 に答える