Recently, I ran into the problem of name collisions from different namespaces. In C #, you can enter synonyms for namespaces, i.e. instead of using the full class name, enter the prefix with which you can access this namespace.
I did not find a simple way, like using Visual Studio and ReSharper to enter a synonym in an already written class. In this connection, I decided to implement my addition to ReSharper, which would solve this problem. In this article I would like to talk about the pitfalls that had to face, realizing, at first glance, such a simple refactoring. (source code and implementation at the end of the article)
So. I wanted to get the code to help do something like this:
using b.b2; class Example { MyTest test; }
| using x = b.b2; class Example { x.MyTest test; }
|
To my joy, the ReSharper object model made it easy to get for each type its FQN (fully qualified name), i.e. in the example, we can definitely say that the class MyTest is in the b.b2 namespace and its FQN b.b2.MyTest
The first formulation of the algorithm may look as follows:
If we want to enter an alias x for the namespace b.b2, then it should be for all types used in the part of the code covered by this using: if the use of the type lies in the space b.b2 and its entry in the code does not use FQN, then you need to add prefix x.
')
There is a problem - we forgot about the Extension methods. Extension methods can only be called if the namespace is explicitly imported into the file. When we replace direct import with a synonym, the compiler cannot find out which method it needs to call. The problem is easily solved - extension methods from classes that are in the b.b2 namespace. it is necessary to call as members of the static class in which they lie.
It was | will be | need to |
---|
using b.b2; class Example { MyTest test; Example() { test.Ext(); } }
| using x = b.b2; class Example { x.MyTest test; Example() { test.Ext();
| using x = b.b2; class Example { x.MyTest test; Example() { x.Extension.Ext(test); } }
|
Next come the rake, which I stumbled upon, trying to start refactoring on test files. For example, one cannot simply compare whether FQN is used or not.
It was | will be | need to |
---|
using r = b.b2; using b.b2; class Example { r.MyTest test; }
| using r = b.b2; using x = b.b2; class Example { xrMyTest test;
| using r = b.b2; using b.b2; class Example { r.MyTest test; }
|
Such an error occurs because FQN for MyTest is b.b2.MyTest. Our refactoring, knowing this information, adds a suffix. This error can be corrected if instead of the operation of adding a prefix to use a full substitution using the type on x. [ShortTypeName].
Name conflicts
Separately, there is a problem when x values will conflict with something already defined
using b.b2; class x { class MyTest{} } class Example { MyTest test; }
| using x = b.b2; class x { class MyTest{} } class Example { x.MyTest test;
|
Proper refactoring should check your result for such errors. In the implementation, I ignored this problem, shifting it onto the shoulders of the developer. In addition, in case of conflicts, the compiler will report this.
query-expression
There is one more problem. What if the user wants to enter a synonym for the System.Linq namespace (and others like it). If Extension methods are used, then our algorithm will do fine. But if query-expression is used, then nothing good will come of it.
It was | will be |
---|
using System.Linq; ... var query = from c in svcContext.ContactSet join a in svcContext.AccountSet on c.ContactId equals a.PrimaryContactId.Id where a.Name.Contains("Contoso") where c.LastName.Contains("Smith") select new { account_name = a.Name, contact_name = c.LastName };
| using aaa = System.Linq;
|
Proper refactoring should uncover the query expression in the chain of calls to static methods (which the compiler actually does) and get something like this.
var query = aaa.Enumerable.Select( aaa.Enumerable.Where( aaa.Enumerable.Where( aaa.Enumerable.Join( svcContext.ContactSet, svcContext.AccountSet, c => c.ContactId, a => a.PrimaryContactId.Id, (c, a) => new { c, a }), @t => @taName.Contains("Contoso")), @t => @tcLastName.Contains("Smith")), @t => new { account_name = @taName, contact_name = @tcLastName });
I don’t think the developer will be happy if the refactoring so cruelly mocks its code. Therefore, in my implementation, I simply do not allow entering a synonym for these namespaces.
A few words about the implementation
There is quite a bit of information on the network about writing plugins for resharper. The main source of information is a decompiler. The SDK helps a little, in which there are several examples of writing extensions (but only one refactoring).
Source codes are available on GitHub, but I highly recommend not using them to explore the internal cuisine of ReSharper.
The result suited me. I hope someone come in handy.
resharper-plugins.jetbrains.com/packages/IntroduceNsAliasgithub.com/ulex/IntroduceNsAliasAdditional information for ReSharper-a plug-in:
• decompiler
•
confluence.jetbrains.com/display/NETCOM/ReSharper+Plugin+Development•
tv.jetbrains.net/videocontent/getting-started-with-resharper-sdk