0

I don't understand a decision that Visual Studio Express 2013 makes when running the "Extract Method" refactoring. Consider the following code:

public struct Foo
{
    private readonly int x, y;
    public int X { get { return x; } }
    public int Y { get { return y; } }

    public Foo(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

class Test
{
    static void Main()
    {
        Foo a = new Foo(1, 2);
        Foo b = new Foo(3, 4);
        Console.WriteLine(a);
        Console.WriteLine(a + "" + b);
    }
}

If I highlight the first Console.WriteLine call and run the Extract Method refactoring, extracting a method called OneParameter, then do the same with the second call extracting a method called TwoParameters, I end up with this code:

class Test
{
    static void Main()
    {
        Foo a = new Foo(1, 2);
        Foo b = new Foo(3, 4);
        OneParameter(a);
        TwoParameters(ref a, ref b);
    }

    static Foo OneParameter(Foo a)
    {
        Console.WriteLine(a);
        return a;
    }

    static void TwoParameters(ref Foo a, ref Foo b)
    {
        Console.WriteLine(a + "" + b);
    }
}

Why does the two-parameter method have ref parameters, but not the one-parameter method? What is the point of using ref here?

Also, why does the one-parameter method return Foo?

Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
user1323245
  • 638
  • 1
  • 8
  • 20
  • 4
    I suspect your assumption is invalid - but we can't tell without a "before and after" of the kind of change it's making. Your question is very hard to answer without an example. You need to understand that `ref` still makes a very significant semantic difference, even with reference types. See http://pobox.com/~skeet/csharp/parameters.html – Jon Skeet Jan 10 '15 at 15:10
  • I think you are converting small piece of information in to the method by refractoring. So, it is not taking chance on your local variables and passing it by ref. By this code will compile and run as it was before with dependencies intact. – Amit Jan 10 '15 at 15:27
  • If the code you're refactoring into a method changes a variable that is a value type, it must pass it by ref into the refactored method in order to actually change it. A reference type could be passed and changed without the ref – Mark Peters Jan 10 '15 at 15:35
  • @amit The example provided in the edited post shows that nothing is changing the variables. They aren't even used in the method. – user1323245 Jan 10 '15 at 15:38
  • @jonskeet I (think) I'm aware of the difference ref has on reference types, hence the first line in my post. Please have a look at the example I added to the OP. – user1323245 Jan 10 '15 at 15:40
  • Okay, I misread your post - but now your example doesn't use *any* of the parameters - as indeed you say in your post. Why include the *irrelevant* code (which doesn't use the parameters) but not the *relevant* code (which does)? Please give a more realistic example. I have an idea of what might be going on, but it's hard to say without a concrete example. (Ideally a full example, with a complete before and after class (including the caller)... the way you've *shown* your example isn't terribly clear either.) – Jon Skeet Jan 10 '15 at 15:43
  • Additionally, `Int32` is still a primitive type, so it's not clear why you're saying that's different. Explaining what steps you're taking to perform the refactoring (i.e. which refactoring you're using) and which version of VS you're using would help too. Basically, if you can help us reproduce this, it would make it much easier to help you. – Jon Skeet Jan 10 '15 at 15:46
  • @jonskeet I believe you're misreading Int2 as Int32. Will post a full example in a few minutes. Work in progress, so huge methods. – user1323245 Jan 10 '15 at 15:49
  • @user1323245: Yes, I did indeed - perhaps it would have been wise to choose names which *weren't* so close to common system value type names? (My only other excuse is jetlag, and that's clearly not your fault.) Don't forget that you don't have to create an example from your *real* methods. Try just reproducing it with an utterly trivial method to start with... – Jon Skeet Jan 10 '15 at 15:50
  • That's still not a *complete* example - it's not something we can just copy, paste, do the refactoring (which you still haven't described - you've just said "doing the refactoring") and see the result. Additionally, it sounds like your `Rect` is *very* unwieldy for a struct... and we still don't know which version of VS you're using. (For example, the laptop I'm on only has the VS2015 preview on it... I've no idea whether that will be able to reproduce it or not.) – Jon Skeet Jan 10 '15 at 16:07
  • @jonskeet Yes, my bad. I've replaced the long winded poor example with a short and working one that you can use. As for the Rect struct, I totally agree, but it's a built in type in the framework I use, so would be a bit tedious to not use it. – user1323245 Jan 10 '15 at 16:23
  • You *still* haven't said what sort of refactoring you're using. Do you understand that there are lots of different refactoring options? What *exactly* are you doing in Visual Studio? – Jon Skeet Jan 10 '15 at 16:25
  • @jonskeet I apologize. Added that info to the post. Guess I shouldn't post when I'm tired. Using the built in Extract Method tool, Edit -> Refactor -> Extract Method, default keybind Ctrl-R,M. – user1323245 Jan 10 '15 at 16:28
  • On what though? You normally do that by selecting *part* of a method - there's no point in extracting a whole method to another method... Please just imagine that someone is going to try to go through the *exact steps* required to see what you're seeing. – Jon Skeet Jan 10 '15 at 16:29
  • @jonskeet Marking both methods in the example and extracting them to a new method will cause ref. Basically, it seems that extraction including the Ref() method will cause ref. – user1323245 Jan 10 '15 at 16:32
  • I don't understand what it would even *mean* to run the "extract method" refactoring when you've got two methods highlighted. Do you understand that the purpose of the refactoring is to take *part* of a method, refactor it into a new method, and replace the original code with a call to that new method? You can't really do that with completely empty methods... – Jon Skeet Jan 10 '15 at 16:33
  • @JonSkeet I guess I don't. I mean, I can add more stuff to the Main method, things that I at some point want to combine into a single method. As long as the code I extract into a method includes a call to a method that, seemingly (tried with another method taking string, Int2, Int2 params), takes 2+ Int2 params it will cause ref. But clearly there's something I've yet to learn regarding method extraction. I'll just asssume VS > me and keep on doing what I'm doing. One day I'll likely understand what I'm doing wrong. Thanks a lot for taking the time and patience. – user1323245 Jan 10 '15 at 16:40
  • I think it would be a valuable lesson to keep working on this question until it can actually be answered. It's really important to learn how to ask a question so that you can obtain appropriate help. At the moment it's simply not clear what you expect the refactoring to do, as there's nothing to extract into a method. You could, for example, have `Console.WriteLine(a);` and then `Console.WriteLine(a + b);` in `Main` and extract each of those lines into a separate method. *That* would then create the two methods you've shown, but with one line in the body of each method. Is that what you meant? – Jon Skeet Jan 10 '15 at 16:43
  • @JonSkeet You're right of course, but in my mind I've been (now, at the end), as clear as I can be. Any code from Main that make use of two Int2 will cause ref when extracted into a method. Extracting Console.WriteLine(a + " " + b); will cause ref. The reason I did it as above, with two methods only, was because I tend to write a bunch of code, including method calls, which I then extract into a single method. In this particular case it was two methods, but adding more would only cause noise in my opinion. The Console.WriteLine() example would've been more clear though. – user1323245 Jan 10 '15 at 16:49
  • Your question is *far* from clear, for the reasons I've said before - it's not clear what you're doing when you say you're refactoring. If I can reproduce it, would you like me to rewrite your question into what *I* think it should have looked like from the start? – Jon Skeet Jan 10 '15 at 16:54
  • @JonSkeet I always appreciate opportunites to learn. – user1323245 Jan 10 '15 at 16:56
  • Unfortunately I'm having trouble booting my machine with VS2013 on, and I can't reproduce it with VS2015. It would be good to know whether you *can* reproduce this with my `Console.WriteLine` suggestions. (i.e. you put those in your main method, without the other two methods at all, and extract one method for each of those two lines). – Jon Skeet Jan 10 '15 at 17:05
  • @JonSkeet I can, and I did. I guess I was (again) not clear in my second to last comment before this one when I wrote "Extracting Console.WriteLine(a + " " + b); will cause ref." Granted, wall of text and what not. Assuming you actually meant _Console.WriteLine(a + " " + b);_? Extracting that causes ref. Extracting a method using one Int2 works fine, anything with two Int2's causes ref it seems. _Console.Write(name + a.X + b);_ = ref. – user1323245 Jan 10 '15 at 17:08
  • Okay, in that case I'll edit your post, and you can check it with what I've written, just to confirm. – Jon Skeet Jan 10 '15 at 17:09
  • @JonSkeet I must point out that this isn't the first time this has happened. I've been meaning to find out when and why, but haven't gotten around to it until now. That's why I wrote "Since a version or two ago" in the OP. Noticed it a year or so ago when extracting some method in a web app I was working on. – user1323245 Jan 10 '15 at 17:10
  • That isn't terribly relevant though - what's more important is to say which version it can definitely be reproduced in. I've edited the question completely - please try reproducing the problem with the *exact* description and code I've given, to make sure it still occurs as I've written. – Jon Skeet Jan 10 '15 at 17:18
  • @JonSkeet Thanks for the nice example of how to post. I ran the code, and it is as the post says for the two-parameter version. However, something strange is happening with the one-parameter version (I might've missed this in my own attempts earlier). The method actually returns the variable passed in. I updated the post to show the method VS created for me as well as adding a second question. Feel free to edit if you care, I really need my sleep now. Again, thanks a lot for all your help. – user1323245 Jan 10 '15 at 17:29
  • Right. That's now a *much* better question. I don't have an answer yet, but at least it's understandable. – Jon Skeet Jan 10 '15 at 17:36
  • For what it's worth, I am able to reproduce using VS2013, using any user-defined `struct`. I don't know enough about compiler implementation to be able to state in a definitive way _why_ this happens. But I assume it's because the `+` operator could be overloaded, and the overload could potentially mutate the `struct` value(s). Hence, passing by-reference is required to ensure the mutation would be preserved. As with refactoring tools in general, if you are sure it's safe to pass by-value (it will be if your `struct` is immutable), feel free to change the generated method signature to suit. – Peter Duniho Jan 10 '15 at 22:22
  • @PeterDuniho Your comment about the compiler doing it to be on the safe side in terms of mutability has valid points. Although, in this case it still make little sense since just one of the extracted methods is using the 'ref' keyword for the parameters despite minimal difference between the extracted code. – user1323245 Jan 11 '15 at 09:26
  • 1
    @user1323245 - the difference between the 2 is cosmetic, as you noted the single-param version returns a Foo. So both cater for the chance that the methods (WriteLine) might change the original arguments. We can immediately see that doesn't happen but the refactoring code just doesn't check. It's playing it safe. The `ref`s and the `return` are two faces of the same strategy. – H H Jan 11 '15 at 12:54
  • I've now reproduce it without using `+` - but it happens even for a single parameter for me. Odd. I had the Roslyn preview installed though - we'll see if uninstalling that makes a difference. – Jon Skeet Jan 11 '15 at 14:00
  • 1
    Okay, without Roslyn I see the same behaviour as you. This is definitely odd. – Jon Skeet Jan 11 '15 at 14:02
  • One final observation - it *doesn't* happen with other system value types (e.g. `Guid`) but it *does* happen even if `Foo` is entirely empty (`public struct Foo {}`) – Jon Skeet Jan 11 '15 at 14:05

1 Answers1

3

Both the return and the two refs happen in case the struct is mutable and modified. VS 2013 doesn't really do any analysis of either the struct type for immutability, or the selected code for mutating operations. It doesn't happen for primitives/Guid/etc simply because there is a hard-coded list of value types in the framework that are known to be immutable.

Kevin Pilch
  • 11,485
  • 1
  • 39
  • 41
  • Human memory is a tricky thing, but I'd like to think that in older versions of Visual Studio I didn't see this behavior. It was a while ago though, and of course I was a worse programmer then than I am now. Anyway, I believe you may have answered my question; there is nothing weird going on with 'ref' that I didn't know about, it's just VS taking the safe route. – user1323245 Jan 11 '15 at 16:55