CodeRush – Organize Members

Mark Miller
20 January 2020

In earlier releases, we’ve seen enhancements to CodeRush’s Organize Members functionality. In this blog post, we’ll talk about some of these new features in greater detail.

Rule Priority

In CodeRush version 19.2.3 we added a new Rule Priority option to Organize Members. To show how this is useful, first let's take a look at the earlier CodeRush functionality without this feature.

Before introducing rule priority, Organize Members used the following approach to split type members based on the members kind and visibility:

  • Get the type members list
  • Evaluate members based on the rules you have set, sequentially

Each rule may match zero or more members from the list. And once a type member is matched by a rule, it's "captured" for the purposes of organization, and it cannot be matched by any other rule.

This approach, while useful, may lead to undesired results in some edge cases. For example, consider the following code:


using NUnit.Framework;

namespace NunitTestProject {
  public class Calculation {

    public int Sum(int a, int b) {
      return a + b;
    }

    [Test]
    public void Sum10Plus15() {
       Assert.AreEqual(10 + 15, Sum(10, 15));
    }

    [Test]
    public void Sum10Plus5() {
      Assert.AreEqual(10 + 5, Sum(10, 5));
    }
  }
}

Suppose you want to wrap all the tests marked with the [Test] attribute into a single region and place those tests at the end of the file.

To solve this task you might try the following:

  1. Create a new “Test methods” rule and put it inside a region:

    CreateTestMethodRule

  2. Run Organize Members on the sample code above.

If you try it, you would likely find the original file unchanged.

Why?

The problem is the “Public methodsrule (up two from our new Region Test methods rule) has already matched all public methods (captured them) before the “Test methods” rule even got a chance to find and organize those public test methods.

In previous versions of CodeRush, to fix this issue, you had one of two workarounds:

  1. Organize Test methods so they appear earlier in the file (before public methods). This is not a great solution if you really want your test methods at the end of the file.
  2. Modify the earlier “Public methods” rule to ignore any methods with the [Test] attribute.

Yeah, we know. It’s more work than it should be. This is CodeRush after all - things should be easy.

Fortunately, in the 19.2.5 release we added a “Rule Priority” option that tells Organize Members when to look for elements that satisfy each rule. You can set this option to High, Normal or Low.

SetRulePriorityToHigh

The new Organize Members now runs rules in three passes. Rules having a high rule priority will be run first, followed by medium-priority rules, and ending with low priority rules.

This allows Organize Members to find our test methods (and group them together) before looking for public methods.

Note that “Rule priority” only impacts the order in which the rule is checked and has zero impact on member order, which is still determined by the order in which the rules appear in the list.

So now to organize our code, we simply set the “Region Test methods” Rule priority to High and run Organize Members.

OrganizeMembersNowWorks 

Grouping Interface Implementors

Some developers like to group interface implementations separately from other members, other developers might want to only group explicitly implemented interface members. This can improve code readability, since all the related members are together. In the 19.2.5 release, we have added interface grouping to Organize Members.

Explicit Interface Implementations

We have added the Explicit interface implementations rule to the Default rule set. This rule allows Organize Members to collect all explicit interface implementations and group them by the interface name.

This new rule uses the new Rule priority option (set to High) and groups members by the Is Explicit Interface Implementor criteria.

Let’s look at the new default rule on the following code:

namespace Project {
    interface IDimensions {
        float GetLength();
        float GetWidth();
    }
    public class Box : IDimensions {
        float lengthInches, widthInches;
        Box(float length, float width) {
            lengthInches = length;
            widthInches = width;
        }
        float IDimensions.GetLength() { return lengthInches; }
        float GetLength() { return lengthInches; }
        float IDimensions.GetWidth() { return widthInches; }
        float GetWidth() { return widthInches; }
    }
}

Explicit and Implicit Interface Implementations

In some cases, you might want to group and sort all interface implementations by the interface name. This is easy enough on the Organize Members options page.

InterfaceImplementations

Just set the rule must priority to high with a grouping criteria of Is Interface Implementor, sorting these members by InterfaceName.

As an example, consider the following code:

using System;

namespace Project {
  public interface IFirst {
    void FirstMethod();
    event EventHandler FirstEvent;
    object this[string index] { get; }
  }

  public interface ISecond {
    void SecondMethod();
    event EventHandler SecondEvent;
    object this[int index] { get; }
  }

  public class Class1 : IFirst, ISecond {
    object ISecond.this[int index] => throw new NotImplementedException();
    public object this[string index] => throw new NotImplementedException();
    public event EventHandler FirstEvent;
    event EventHandler ISecond.SecondEvent {
      add {
        throw new NotImplementedException();
      }
      remove {
        throw new NotImplementedException();
      }
    }

    void ISecond.SecondMethod() {
      throw new NotImplementedException();
    }

    public void FirstMethod() {
      throw new NotImplementedException();
    }

    public void MyMethod() {
      var s = new { num = 10 };
      var newType = new NewType();
    }
  }
}


With the example interface implementor-grouping rule above, Organize Members would look like this:

OrganizeMembersInterfacImplementors


Grouping Event Handlers

We have also added the ability to group event handlers. Just create a simple event handlers rule and place this rule in the desired location in the rule list, like this:

organize events rule

For example in this code:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp4
{
  public partial class Form1 : Form {
    public Form1() {
      InitializeComponent();
      Button button = CreateButton();
      this.Controls.Add(button);
    }

    private Button CreateButton() {
      var myButton = new Button();
      myButton.Text = "Click me!";
      myButton.Click += myButton_Click;
      myButton.MouseEnter += MyButton_MouseEnter;
      myButton.MouseLeave += MyButton_MouseLeave;

      return myButton;
    }

    private void myButton_Click(object sender, EventArgs e) {
      MessageBox.Show("Button clicked!");
    }

    private void MyButton_MouseEnter(object sender, EventArgs e) {
      var myButton = sender as Button;
      myButton.ForeColor = Color.Red;
    }

    private void MyButton_MouseLeave(object sender, EventArgs e) {
      var myButton = sender as Button;
      myButton.ForeColor = Color.Yellow;
    }
  }
}

Organize Members (with the event handler rule shown earlier) would group all the class’s event handlers like this:

GroupEvents

Dynamic Regions Creation

CodeRush can wrap all members matching a rule into a region if it knows the region name (for example, “public methods”). But what if you need a region name that’s based on the code you’re organizing? CodeRush now makes it easy to wrap all matching elements in a dynamically-generated region with new sorting/organizational options.

For example, if you sort by an implemented interface name, you can wrap distinct groups (e.g., each group of members that implement a single interface) into a separate region.

If you sort by kind, you can wrap distinct groups in regions based on member type (e.g., fields, properties, methods, etc.).

You can also use built-in variables to specify the region name. These variables get their values from your code.

OrganizeByInterface

For example, let’s apply the “Interface implementations” rule, shown in the screenshot above, to the following code:

using System.Drawing;

namespace ConsoleApp {

  interface IDimensions {
    float GetLength();
    float GetWidth();
  }

  interface IColor {
    Color GetBackgroundColor();
    Color GetForegroundColor();
  }

  public class Box : IDimensions, IColor {
    Color boxColor;
    float lengthInches;
    float widthInches;

    Box(float length, float width, Color color) {
      lengthInches = length;
      widthInches = width;
      boxColor = color;
    }

    float GetLength() {
      return lengthInches;
    }
float IDimensions.GetLength() { return lengthInches; }
float GetWidth() { return widthInches; } float IDimensions.GetWidth() { return widthInches; } public Color GetForegroundColor() { return boxColor; }
public Color GetBackgroundColor() { return boxColor; } } }

The result will look something like this:

DynamicRegionGeneration


Conclusion

We hope these features will help you more quickly and easily organize your code and produce code that is easier to read and maintain.

As always, we welcome your feedback. If you have specific questions or need assistance with CodeRush, feel free to let us know in the comments below or contact us through our Support Center (support@devexpress.com).

Showcase Your Apps on DevExpress.com

Highlight your business app and share your development experiences with the DevExpress community. To include your app in our upcoming App Showcase, please forward an application screenshot to clientservices@devexpress.com and tell us which DevExpress products you currently use within your organization.
No Comments

Please login or register to post comments.