"Find All References" Design, regarding interface members
One of the things that I've been working on to help finish Whidbey, are the new refactoring features. However, I've recently found myself questioning the design for "Find All References", in regards to its handling of interface members. I discovered this behavior while writing a toy project in C#, but the following example illustrates it better, in my opinion. Imagine you're writing a generalized library - and provide two different interfaces, IResource and IStream. Both of them have the notion of a Close() method (for simplicity sake, I'll put the interfaces in separate files, which will help make it easier to see the design confusion):
|
namespace Library {
public interface IResource {
void Close();
}
}
|
|
namespace Library {
public interface IStream {
void Close();
}
}
|
Simple enough, right? The conundrum arises when you have an object that implements both interfaces, and uses the the same member to implement them, such as the following:
|
using Library;
class NetworkSocket : IResource, IStream {
public void Close() {
}
}
|
What struck me as awkward, is that if you then do "Find All References", on IStream.Close(), you'll get the following results:
- (Definition) NetworkSocket.cs - (5,17): public void Close() {
- (Definition) IResource.cs - (5, 14): void Close();
- (Definition) IStream.cs - (5, 14): void Close();
Thus, intellisense reports that IStream.Close() is defined in all of those three places. However, why is IResource.Close() in this list? Some IDE-team members argue that the design was that we would return the transitive closure of all of the methods that are captured during this find process. I, however, argue that this is definitely unintuitive. This gets worse too, for example, introduce the following:
|
using Library;
class OtherResource : IResource {
public void Close() {
}
}
|
|
using Library;
class OtherStream : IStream {
public void Close() {
}
}
|
|
using Library;
class StreamReader {
public StreamReader(OtherStream stream) {
// ...read the stream ...
stream.Close();
}
}
|
Now, invoke "Find All References" on IResource.Close(). You'll get the following results:
- (Definition) NetworkSocket.cs - (5,17): public void Close() {
- (Definition) OtherResource.cs - (5,17): public void Close() {
- (Definition) OtherStream.cs - (5,17): public void Close() {
- (Reference) StreamReader.cs - (6, 16) : stream.Close();
- (Definition) IResource.cs - (5, 14): void Close();
- (Definition) IStream.cs - (5, 14): void Close();
IStream.Close() is on the list (which is the reverse case of the previous "strange" element), but what really makes me think this is wrong, is that StreamReader's use of OtherStream is there. OtherStream doesn't even implement IResource, so it's impossible for OtherStream.Close() to be related to IResource.Close(). In that context, you *know* you're not dealing with an IResource, however, it's still a reference <shrug>. This gets worse as you have more types that implement IResource or IStream, because whenever you do Find All References on Close(), you get even more unexpected results to wade through.
So, you wonder, is there any method behind the madness? It's all about Rename refactoring. If you rename IStream.Close() to something else, you better rename IResource.Close() to the same thing, otherwise the NetworkSocket class will suddenly no longer implement all the members of IResource. I think that's a valid concern, but IMHO, if you're sharing implementations on the same member, and then you rename one of the interfaces's declaration of that member, then I would expect a compiler error to be introduced, saying that you can no longer implement both interfaces with the same member now. Just because one developer happens to implement my interface, and someone else's interface, doesn't mean that if someone else renames their interface member that I too should be renamed.
So, mine is obviously a directed perspective, but how do the rest of you out there feel about this?