Search and replace C# expression

278 views Asked by At

Time and again I find myself needing to change the use of one method call with another.

E.g. I have 100 occurrences of method calls similar to this:

Helper.GetIntFromData(packetData.Skip(offset).Take(length).ToArray());

that need to be changed to

Helper.GetIntFromData(packetData, offset, length);

This is relatively easily solved with a regular expression. But what if whitespace (sometimes) comes into play ?

Helper.GetIntFromData(packetData
    .Skip(  offset  )
    .Take(  length  )
    .ToArray()  
);

Still doable with a regex, but it's now an unreadable mess of optional whitespace tokens.

OK, but what if the parameters are not always simple identifiers, but arbitrary expressions?

Helper.GetIntFromData(obj.GetData(true).Skip( 7 + GetOffset( item.GetData() ) )
    .Take( length1 / length2 ).ToArray());

This is where regular expressions really break down.

My question is:

Can this be done today? (in a way that keeps you sane, i.e. without regex)

Is there a VS extension or standalone tool that can handle searching and replacing C# code at a higher (semantic) level?

Something that would allow me to search for (I imagine):

Helper.GetIntFromData($expr1.Skip($expr2).Take($expr3).ToArray())

and replace with

Helper.GetIntFromData($1, $2, $3)

Does such a tool exist for C#? (I imagine it could be built around Roslyn.)

3

There are 3 answers

1
Amit On

After giving it another thought, what you're trying to achieve is dangerous and better be avoided. A RegEx match / replace is a limited feature. It can not "understand" complex code, and is bound to fail. Consider an example like (lets ignore for a second if that code even makes sense):

Helper.GetIntFromData(packetData.Skip(skipArray.Take(1)).Take(length).ToArray());

A regular expression that's not specifically tailored to avoid the "wrong Take" doesn't stand a chance. It is a matter of time until your auto-replacing method breaks your code, and your only hope is that it's brutal enough to generate a compile time error. It might just as well create tough to find runtime exception or worse, unexplained behavior. Code refactoring should be done with a tool that understands code, not a tool that recognizes text patterns.

0
ClickRick On

If at all possible, I'd do it "manually", but using simple regexes (yes, I know you said without them, and Amit's answer "looks right" though I've not checked it for edge cases).

Something like this:

Helper.GetIntFromData(packetData.Skip(offset).Take(length).ToArray());
Helper.GetIntFromData(obj.GetData(true).Skip( 7 + GetOffset( item.GetData() ) )
    .Take( length1 / length2 ).ToArray());

Replace (Helper.GetIntFromData.*?)\.Skip\( with \1, to get

Helper.GetIntFromData(packetData, offset).Take(length).ToArray());
Helper.GetIntFromData(obj.GetData(true),  7 + GetOffset( item.GetData() ) )
    .Take( length1 / length2 ).ToArray());

Then replace (Helper.GetIntFromData.*?)\)\.Take\( with \1, to get

Helper.GetIntFromData(packetData, offset, length).ToArray());
Helper.GetIntFromData(obj.GetData(true),  7 + GetOffset( item.GetData() ) 
    ,  length1 / length2 ).ToArray());

And finally replace (Helper.GetIntFromData.*?)\)\.ToArray\(\)\); with \1\); to get

Helper.GetIntFromData(packetData, offset, length);
Helper.GetIntFromData(obj.GetData(true),  7 + GetOffset( item.GetData() ) 
    ,  length1 / length2 );
1
bradgonesurfing On

Resharper has semantic search and replace

Helper.GetIntFromData(packetData.Skip($offset$).Take($length$).ToArray());

with

Helper.GetIntFromData(packetData, $offset$, $length$);

It is white space insensitive and you can constrain the token matches to be of certain types. I use it all the time to do what you are trying to do. I have not yet seen any roslyn based projects that all the user to do this so trivially.

Here is an intro to the feature on resharpers website

http://blog.jetbrains.com/dotnet/2010/04/07/introducing-resharper-50-structural-search-and-replace/