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

Extract Method changes 'using var' semantic to 'out parameter' semantic #39329

Closed
vsfeedback opened this issue Oct 16, 2019 · 7 comments · Fixed by #76343
Closed

Extract Method changes 'using var' semantic to 'out parameter' semantic #39329

vsfeedback opened this issue Oct 16, 2019 · 7 comments · Fixed by #76343
Assignees
Labels
Area-IDE Bug Developer Community The issue was originally reported on https://developercommunity.visualstudio.com Feature - Extract Method help wanted The issue is "up for grabs" - add a comment if you are interested in working on it IDE-CodeStyle Built-in analyzers, fixes, and refactorings
Milestone

Comments

@vsfeedback
Copy link

This issue has been moved from a ticket on Developer Community.


Using 'Extract Method' in a block containing 'using var' causes the resulting method to assign the variables to out parameters and contains no using expression.


Original Comments

Visual Studio Feedback System on 10/6/2019, 11:35 PM:

We have directed your feedback to the appropriate engineering team for further evaluation. The team will review the feedback and notify you about the next steps.

Visual Studio Feedback System on 10/8/2019, 02:04 PM:

This issue is currently being investigated. Our team will get back to you if either more information is needed, a workaround is available, or the issue is resolved.


Original Solutions

(no solutions)

@ryzngard
Copy link
Contributor

Verified in 16.4 preview 2 with the below code

Before

    public class Goo : IDisposable
    {
        void M2() { }
        void M3() { }
        string S => "S";

        void M()
        {
            using var g = new Goo();
            var s = g.S;
            g.M2();
            g.M3();
        }

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

After

    public class Goo : IDisposable
    {
        void M2() { }
        void M3() { }
        string S => "S";

        void M()
        {
            Goo g = NewMethod();
        }

        private static Goo NewMethod()
        {
            var g = new Goo();
            var s = g.S;
            g.M2();
            g.M3();
            return g;
        }

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

@RikkiGibson
Copy link
Contributor

The expectation is that the Goo g = NewMethod(); would change to using Goo g = NewMethod(), right?

@ts2do
Copy link

ts2do commented Oct 17, 2019

Here's the expected result of extracting the entire body from method M:

    public class Goo : IDisposable
    {
        void M2() { }
        void M3() { }
        string S => "S";

        void M()
        {
            NewMethod();
        }

        private static void NewMethod()
        {
            using var g = new Goo();
            var s = g.S;
            g.M2();
            g.M3();
        }

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

@jinujoseph jinujoseph added this to the 16.5 milestone Oct 23, 2019
@jinujoseph jinujoseph added IDE-CodeStyle Built-in analyzers, fixes, and refactorings help wanted The issue is "up for grabs" - add a comment if you are interested in working on it labels Oct 23, 2019
@sharwell sharwell added the Developer Community The issue was originally reported on https://developercommunity.visualstudio.com label Nov 21, 2019
@davidwengier
Copy link
Contributor

davidwengier commented Nov 22, 2019

The expectation is that the Goo g = NewMethod(); would change to using Goo g = NewMethod(), right?

My expectation is that, as above, NewMethod() wouldn't have a return type at all. I've been running into this a lot in some code I've got doing a lot of SkiaSharp drawing, and the problem is clearer IMO when there are multiple things with usings:

Before:

        private void M()
        {
            using var x1 = new System.IO.MemoryStream();
            using var x2 = new System.IO.MemoryStream();
            using var x3 = new System.IO.MemoryStream();
        }

After extracting the all 3 lines to a method:

 		private void M()
        {
            MemoryStream x1, x2, x3;
            NewMethod(out x1, out x2, out x3);
        }

        private static void NewMethod(out MemoryStream x1, out MemoryStream x2, out MemoryStream x3)
        {
            x1 = new System.IO.MemoryStream();
            x2 = new System.IO.MemoryStream();
            x3 = new System.IO.MemoryStream();
        }

It's not that the usings are gone, though obviously thats not good, its that the analysis thinks the variables are used outside of the selection, even though all of the method contents were selected.

@ts2do
Copy link

ts2do commented Nov 26, 2019

After further consideration, it seems like an extremely complex construct to handle properly.

Consider this method:

int Method(IEnumerable<char> source1, string source2)
{
    using IEnumerator<char> enumerator1 = source1.GetEnumerator();
    using CharEnumerator enumerator2 = source2.GetEnumerator();

    enumerator1.MoveNext();
    enumerator2.MoveNext();

    return enumerator1.Current + enumerator2.Current;
}

If you try to extract all but the last line of the method body, you would end up with something akin to this:

int Method(IEnumerable<char> source1, string source2)
{
    IEnumerator<char> enumerator1 = null;
    CharEnumerator enumerator2 = default;
    bool disposeEnumerator2 = false;

    try
    {
        ExtractedMethod(source1, source2, out enumerator1, out enumerator2, out disposeEnumerator2);

        return enumerator1.Current + enumerator2.Current;
    }
    finally
    {
        using (enumerator1)
        using (ConditionalDispose.Create(enumerator2, disposeEnumerator2))
        {
        }
    }
}

void ExtractedMethod(IEnumerable<char> source1, string source2, out IEnumerator<char> enumerator1, out CharEnumerator enumerator2, out bool disposeEnumerator2)
{
    enumerator1 = source1.GetEnumerator();
    enumerator2 = source2.GetEnumerator();
    disposeEnumerator2 = true;

    enumerator1.MoveNext();
    enumerator2.MoveNext();
}

This would be the most accurate handling for such an operation, though it's not necessarily best practice to use out parameters when exceptions are thrown. As you can see, method extraction gets hairier when a value type is involved because the resulting code must not call Dispose on arbitrary default value types, needing an auxiliary ConditionalDispose<> type for support. It's possible to try to encapsulate the extra out bool parameter generated, but nothing would really be suitable.

I think the best solution is to either ask if the user would like to expand the code selection to the closing bracket ending the topmost using-var expression within the selection or not show the 'Extract Method' quick action at all. Anything else would clearly cause a lot of unexpected code generation.

@jinujoseph jinujoseph modified the milestones: 16.5, Backlog Jan 14, 2020
@jnm2
Copy link
Contributor

jnm2 commented Jul 25, 2020

Another repro:

using System.Collections.Generic;

class C
{
    bool M(IEnumerable<int> p)
    {
        [|using var x = p.GetEnumerator();
        return x.MoveNext();|]
    }
}

🛠 Extract method:

using System.Collections.Generic;

class C
{
    bool M(IEnumerable<int> p)
    {
        IEnumerator<int> x;
        return NewMethod(p, out x);
    }

    private static bool NewMethod(IEnumerable<int> p, out IEnumerator<int> x)
    {
        x = p.GetEnumerator();
        return x.MoveNext();
    }
}

@jnm2
Copy link
Contributor

jnm2 commented Jul 25, 2020

The danger is that you might forget to put the using token back.

ryzngard added a commit that referenced this issue Jan 3, 2023
When a variable declaration syntax had a using, make sure to keep that using in the new variable declaration syntax.

Part of the needed fixes for #39329
@github-project-automation github-project-automation bot moved this to InQueue in Small Fixes Oct 22, 2024
@CyrusNajmabadi CyrusNajmabadi assigned Cosifne and unassigned ryzngard Nov 26, 2024
@github-project-automation github-project-automation bot moved this from InQueue to Completed in Small Fixes Dec 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE Bug Developer Community The issue was originally reported on https://developercommunity.visualstudio.com Feature - Extract Method help wanted The issue is "up for grabs" - add a comment if you are interested in working on it IDE-CodeStyle Built-in analyzers, fixes, and refactorings
Projects
Status: Completed
Development

Successfully merging a pull request may close this issue.

10 participants