Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactoring] Inline Method #22052

Closed
kuhlenh opened this issue Sep 11, 2017 · 23 comments · Fixed by #46509
Closed

[Refactoring] Inline Method #22052

kuhlenh opened this issue Sep 11, 2017 · 23 comments · Fixed by #46509
Assignees
Labels
Area-IDE Feature Request help wanted The issue is "up for grabs" - add a comment if you are interested in working on it InternalAsk
Milestone

Comments

@kuhlenh
Copy link

kuhlenh commented Sep 11, 2017

We have extract method, it would be nice if we sort of had the reverse: inline method.

This would be invoked from a method call and would essentially copy/paste the body of the method where the method call is.

@wli3 can offer more information.

@kuhlenh kuhlenh changed the title [Refactoring [Refactoring] Inline Method Sep 11, 2017
@wli3
Copy link

wli3 commented Sep 11, 2017

@dpoeschl dpoeschl added this to the 15.later milestone Sep 11, 2017
@ericmutta
Copy link
Contributor

@kuhlenh We have extract method, it would be nice if we sort of had the reverse: inline method.

It would be more than nice! I just used this refactoring a few minutes ago, but have to do so via CodeRush. Having more refactorings available natively really helps because eventually we can reduce the number of extensions we have to load into Visual Studio 👍

@DustinCampbell
Copy link
Member

FWIW, this has always been considered a lower priority refactoring because research has shown that it is not used nearly as often as Extract Method. That said, it's nice to have the inverse.

Note that copying-and-pasting is not how this refactoring would actually be implemented. It's actually quite complex:

  1. Locals should be introduced for the parameters to the original function.
  2. Identifiers in the method body need to be renamed if they conflict with any identifiers in the target method body.
  3. Complexification/simplification needs to be applied to avoid conflicts since this refactoring can potentially affect a large code base.

@ericmutta
Copy link
Contributor

@DustinCampbell FWIW, this has always been considered a lower priority refactoring because research has shown that it is not used nearly as often as Extract Method.

I certainly agree...I am curious though, how do you generally do research for each refactoring to determine its priority?

In some cases, an omission becomes a self-fulfilling prophecy: a refactoring is not implemented so no ever tries/knows to use it. A good example of this is "Break apart arguments" from CodeRush for Rosyln which takes this:

Public Sub Foo(Argument1 As Integer, Argument2 As Integer, Argument3 As Integer)
End Sub

...and turns it into this:

Public Sub Foo(Argument1 As Integer, 
               Argument2 As Integer, 
               Argument3 As Integer)
End Sub

For 17 years I have been doing that transformation manually (thankfully, automatic indenting in the editor helps line up the arguments). Then the other day I discovered "Break apart arguments" and now I use it several times a day.

You are also right that "Inline method" is not an easy thing to do correctly (as is the case for many refactorings). Given some bugs I have seen (like #22012 and #22059) it's really easy to forget some strange corner cases!

@DustinCampbell
Copy link
Member

I certainly agree...I am curious though, how do you generally do research for each refactoring to determine its priority?

There are number of ways that we determine priority. Below are a few example, though this is certainly not the full list.

  1. Feature requests and customer development.
  2. Filling in gaps in our offering. (For me, this is the strongest reason to have Inline Method).
  3. Looking at refactoring research (yes, this is a thing 😄). For example, here a couple of papers that include usage data (link1, link2).

FWIW, I worked on CodeRush at DevExpress for five years of my career. I remember the discussions we had 10 years ago about "Inline Method" and whether it was worth implementing. Essentially, we came to the conclusion that I mentioned about -- that it's valuable to have the inverse refactoring even if it might not be used as often as, say, "Introduce Local" vs. "Inline Temp". I'm glad to hear you've found it useful!

@ericmutta
Copy link
Contributor

@DustinCampbell Looking at refactoring research (yes, this is a thing 😄)

Who would have thunk? 😄 Thanks for the links, I will take a look!

@DustinCampbell FWIW, I worked on CodeRush at DevExpress for five years of my career.

I wasn't sure before if it was proper etiquette to gush over DevExpress, but now I think I can say it without being stoned: the work you guys did over there is just utterly brilliant. Every time I see that little animation for the "reorder parameters" refactoring I am left wondering: how on earth do they do that? 😄 Glad to hear you work on Rosyln now, I am sure you will inspire the team to do more refactorings natively (fingers crossed!).

@DustinCampbell
Copy link
Member

Glad to hear you work on Rosyln now, I am sure you will inspire the team to do more refactorings natively (fingers crossed!).

Well, I hate to dash your hopes, but I've been on Roslyn since the project kicked off in 2010. At this point, I'm just the old man on the team. 😄

@jinujoseph jinujoseph modified the milestones: 15.6, Unknown Nov 1, 2017
@fschwiet
Copy link

fschwiet commented Jan 6, 2020

+1 please

@Epstone
Copy link

Epstone commented Feb 13, 2020

+1 from my side. I find it quite natural that 'extract' is used more often because going from spaghetti to structured methods is the 'normal' way I work and most others I guess. When something went wrong we can also use undo instead of inlining.
Inlining really needs to be teached, trained and repeated before it gets wired into our fingers. But then it feels like magic at your fingertips needed for shaping the dough :)

@genlu genlu added the Need Design Review The end user experience design needs to be reviewed and approved. label Feb 20, 2020
@genlu
Copy link
Member

genlu commented Feb 20, 2020

As @DustinCampbell pointed out above, if unrestricted, "inline method" refactoring could have significant impact on the code base, and potentially introduce errors and breaks.
Just name a few that come to my mind:

  • handling parameters passed by ref vs by value
  • handling nullable annotation
  • when applied on externally accessible methods, it might break public API
  • if we opt to add missing usings at consumer sites, it might introduce conflicts

I think it make sense to provide a scaled-down version to start with which would still cover a lot scenarios people run into often. For example: only allowed on private method with simple body (single statement body and expression body?)

@mwelsh1118
Copy link

I would say the most common use case for "inline method" is to alias another method. For example, say I have:

public class Widget
{
    public static Widget Create()
    {
        return new Widget();
    }
}

I might end up creating a separate WidgetFactory class:

public static class WidgetFactory 
{
    public static Widget CreateWidget() 
    {
        return new Widget();
    }
}

And then aliasing the old factory method to the new one:

public class Widget
{
    public static Widget Create()
    {
        return WidgetFactory.CreateWidget();
    }
}

Finally, I can inline the original method and now all callers of that method are instead calling my new factory class. This combines very well with a "Replace constructor with a factory method" refactoring.

I think inlining private methods is probably not going to add a lot of value, since that's relatively easy to do manually (you're unlikely to have more than a handful of callers). The real value is where there are many external callers of a method and you want to redirect those calls somewhere else.

Another common use case is to turn an instance method into an extension method, which goes something like this:

  1. Define a new extension method Y with the functionality from instance method X
  2. Change instance method X to call extension method Y
  3. Inline method X and now all callers are instead calling extension method Y.

@martijnhoekstra
Copy link

+1 on that there is value in inlining a single call site, regardless of whether that means the extracted method can then be eliminated.

@genlu
Copy link
Member

genlu commented Feb 26, 2020

@mwelsh1118 Makes sense. I didn't think of this refactoring as a way to do some other things indirectly (in your examples, change the target method of invocations). Sounds like the restriction on non-private method can be relaxed to just showing a warning says this would break external consumers, while still being disallowed on methods with complex body?

@mwelsh1118
Copy link

@genlu That seems reasonable.

@kendrahavens
Copy link
Contributor

Design meeting update:

Enable the simple case proposed in @genlu's comment: #22052 (comment)

@fschwiet
Copy link

fschwiet commented Mar 13, 2020

referring to the cases @genlu listed, I would recommend seeing Resharper does and doing that. Sorry not too original sounding, but its implementation never really let me down. Another scenario this can enable is doing change signature operations that don't fit in the current change signature UI (wrap the method you want, inline the wrapper).

handling parameters passed by ref vs by value

I believe resharper creates variables around the inlined code in place of the ref or out parameters.

handling nullable annotation

This I don't know, it maybe makes sense to just drop the annotations.

when applied on externally accessible methods, it might break public API

Resharper would also give a warning then proceed.

if we opt to add missing usings at consumer sites, it might introduce conflicts

I believe Resharper would introduce "using alias=n" for the types it needs that conflict, but you could also just fully qualify those names (with their namespace).

@sharwell sharwell added help wanted The issue is "up for grabs" - add a comment if you are interested in working on it and removed Need Design Review The end user experience design needs to be reviewed and approved. labels Mar 16, 2020
@Cosifne Cosifne assigned Cosifne and unassigned genlu Jul 13, 2020
@Cosifne
Copy link
Member

Cosifne commented Aug 10, 2020

@vatsalyaagrawal This is the inline method isssue

@Cosifne
Copy link
Member

Cosifne commented Sep 11, 2020

Thanks for all the posts and discussion here. I have checked in a PR implements what @genlu purposes.

@Cosifne Cosifne closed this as completed Sep 11, 2020
@tudor-turcu
Copy link

Any ideea when this will be released? (I understood it was implemented..)
In VS2019 16.8.4 is still not available..
image

@genlu
Copy link
Member

genlu commented Jan 27, 2021

Based on the PR, it should be in 16.8. @Cosifne Any idea?

@Cosifne
Copy link
Member

Cosifne commented May 5, 2021

I completely lose track of this issue.

@tudor-turcu
It should be in 16.8. Could you share the code snippet you are trying to use? please note right now this feature only applies to one line scenario.

If you feel there is a bug, then please submit an issue. I can work on that

@tudor-turcu
Copy link

@Cosifne - I tried with a simple Console application (.NET Framework 4.5), created in VS 2019 Professional (v 16.9.4, running on Windows 10 20H2), created with File/New Project.
The project contains a fingle file, Program.cs:

using System;

namespace ConsoleTest
{
class Program
{
static void Main(string[] args)
{
var res = TestFunction(10);

        Console.WriteLine(res);
    }

    private static int TestFunction(int a)
    {
        Console.WriteLine(a);
        return a + 1;
    }
}

}

However, I see no way to invoke Inline Method in the IDE:

  • using Ctrl-.
    image

image

image

@Cosifne
Copy link
Member

Cosifne commented May 5, 2021

@tudor-turcu
Yes, that's by design.
So if you looked at what @genlu 's purposal here

As DustinCampbell pointed out above, if unrestricted, "inline method" refactoring could have significant impact on the code base, and potentially introduce errors and breaks.
Just name a few that come to my mind:

  • handling parameters passed by ref vs by value
  • handling nullable annotation
  • when applied on externally accessible methods, it might break public API
  • if we opt to add missing usings at consumer sites, it might introduce conflicts

I think it make sense to provide a scaled-down version to start with which would still cover a lot scenarios people run into often. For example: only allowed on private method with simple body (single statement body and expression body?)

So it only supports 'one line' scenario. And in your snippet test TestFunction contains two statements.

I understand this might be frustrating but when the ide team design this feature, people have various of worries to support the full method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE Feature Request help wanted The issue is "up for grabs" - add a comment if you are interested in working on it InternalAsk
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.