Suggestion: Improved Scopes for Inherited/Implemented/Extension Members
It's very useful to limit the find symbol results for searches like Find Referrers/Overrides/Extensions to the following cases:
- Current project only (perhaps have Shift modifier for this)
- Current + Derived only
- Current type only (for generic extensions) - hugely useful as an extension may be valid at 100s of places, but I am only interested in a few of them (closed generics) that use specific
thistype. (obviously it would have to be invoked from the caller, as the callee is an open generic).
And so with this approach these extra modifiers could be combined to further limit the scope.
The generics would be especially useful for enums for example, as I often search for common enum extension members I defined, but I need to find their usages on a specific enum, not all the usages on an open TEnum.
Also the existing implementation of current symbol only for Find Referrers does not work properly in that it finds too many items when Ctrl is used to limit results only to the current instance:
interface INameable { string Name { get; } }
abstract class NamedEntity : INameable {
public string Name { get; set; }
}
class Country : NamedEntity { }
class Person : NamedEntity { }
class Program {
void Main() {
var c = new Country();
c.Name = "Country";
var p = new Person();
p.Name = "John"; //<-- If I hover over Name here and choose Find Referrers with Match Symbol only, it will also find c.Name (the one on Country)
}
}
It would be great if you could prioritize this, as this search is really indispensable for large solutions when you need to update/rename/refactor certain symbols and need to understand the specific places where they are used.
There will be too many modifiers.
How about showing sub-command buttons at the menu? You can click the buttons to get the variant commands, as the following screenshot shows.
On the above screenshot, the Copy Symbol command has two variants. Moving your mouse cursor onto them, you will get a tooltip telling you what they are for. No need to struggle with Ctrl and Shift any more, and there will not limit command variants to 3.
That's awesome! Even better! I just thought modifiers would be easier to implement :)
Deepseek helped me to implement that. You will see it in the next version.
I find that some options are not mutually exclusive, and I turn those options into toggle buttons. You can check those buttons one by one for your needs.
The good aspect is that the toggle buttons are intuitive. The bad aspect is that it requires extra button clicks to launch the symbol search.
You are absolutely right - some options are not mutually exclusive. Your approach with toggle buttons is great! I love it! Extra click is a very small price to pay compared to what this gives you :)
Please try the new beta.
I will as soon as I figure out how to get back to VS after their yesterday's Insiders Release which prevents VS from opening any solution (so do not upgrade yet to the latest Insiders version).
I really like the updated experience with toggle-able modifiers! Makes it very easy to use.
However as for the actual symbol search, the current version could not find for example the open generic extension member defined on TEnum when I selected Exact match. At the very least there should be 1 match - namely the one where I invovled the menu from. But in my case there would be several matches, yet none were found.
the current version could not find for example the open generic extension member defined on
TEnumwhen I selected Exact match
Please show me the code and let me see what's going on.
public static class Extensions {
public static string TextName<TEnum>(this TEnum it, string? fallback = null) where TEnum : unmanaged, Enum => "foo";
}
enum MyEnum {
A,
B,
}
class Program {
void Main() {
var a = MyEnum.A.TextName(); //<- When doing Find Referrer on `TextName`, and scoping to exact match, the result list is empty.
var b = MyEnum.B.TextName();
}
}
The "Extract Match" option is intended to limit search results within the current type, excluding inherit or base types. The about situation is obviously a bug. I will fix it.
For your aforementioned requested limit on generics, maybe a new option "Match Type Argument" should be provided.
For your aforementioned requested limit on generics, maybe a new option "Match Type Argument" should be provided.
Yes, that would probably be the most logical way to do it!
p.Name = "John"; //<-- If I hover over Name here and choose Find Referrers with Match Symbol only, it will also find c.Name (the one on Country)
It is by design.
Since the Name property is defined in the abstract class, and those sub-classes have not overridden it, the find reference of the Name property will return occurrences from both classes. If you change the Name property in the abstract class to virtual and have it overridden in the subclass, the Find References command can return references of the overridden property.
There's a new beta. Please help test it.
It is by design.
I see. But it will be very useful to add this feature as another scope. Perhaps implementation-wise it would simply filter the returned list by the type through which the member is accessed (which would be a derived type that inherited the member form its base).
There's a new beta. Please help test it.
I really like the new modifiers! However I still cannot cover the case of finding all call sites of GetName on enum MyEnum. In other words, I need to find specific uses of open generic member, constrained to the current callsite's receiver type (and maybe even optionally just a specific member in case of enums)...
public static class Extensions {
public static string GetName<TEnum>(this TEnum it) where TEnum: unmanaged, Enum => "foo";
}
public enum MyEnum {
Bar, Baz
}
public enum MyEnum2 {
A, B
}
public static class Program {
public static void Main() {
var e = MyEnum.Bar.GetName();
var e2 = MyEnum2.A.GetName();
}
}
Check this option button, which will match the type arguments passed to the generic methods.
I don't see this option in the latest beta. The only 3 options I have are the last 3.
Download the new beta then. The newer one has even more options, which allows you to limit result symbols in source code or referenced libraries.
I still do not see the option in the latest beta:
I should clarify perhaps that:
- Extension Member
TextNameis defined in Base library project - Enum Type
Sexis defined in a different library project - The caller (where the source is located) is in yet 3rd project that references both the Enum-containing project and the Extension-containing project.
Ideally this feature should work regardless of whether these are defined in the same or different projects - any combination should be supported. And this should apply to non-user code too, i.e. framework and Nuget library references.
There's slight difference in the Super Quick Info.
If you right click the large TextName<TEnum>, the symbol will be the generic class, so there's no option for it. If you click the large button on the left of TextName<TEnum> (see my screenshot above), you get access to the TextName<Sex>, so there'll be an option button.
Oh I see. I did not realize there is a difference there (why is that btw?). It is very subtle and easy to miss! Perhaps this can be somehow made more obvious to the user, as I am sure many others won't discover it either.
I tested it and it works very nicely! I also really liked how you included a clickable generic type argument in the title bar of the results! Thank you!
I hope you can also address the current / derived classes of the current only scope as well (exclude sibling and base classes) when the members are not overridden. I often need to search for these and the list can be very large especially for things like NamedEntityBase.Name type thing, where Name is only defined on the base, and is not overridden (regardless of whether it is virtual or not). It's very useful to find all references to such members only for a specific class (with/without its derived classes). If a Roslyn does not have an API for that, perhaps that can simply be implemented with the additional manual filter check - exclude all those results whose receiver type is not the type on which we invoked the command (and/or one of its derived classes).
It is very subtle and easy to miss!
Yes, it is.
I have deliberately substituted the specific symbol with its original generic definition in the Quick Info, so the signature part on the Quick Info would not look very very long, when the type argument contains generic type, for instance, Dictionary<TKey, TValue> versus Dictionary<string, Dictionary<int, List<MySpecificClass>>>, etc.
When a user right click the signature Dictionary<TKey, TValue>, he might be expecting that the symbol analysis command returns results about the generic type, not the specific one.
Nowadays we have more rooms to expand command options, maybe we can design a better experience for such type of scenario.
I'd love to hear your opinion.
Please download the new beta, which attempts to make the signature context menu behave the same as the big button on the C# Quick Info.
I often need to search for these and the list can be very large especially for things like
NamedEntityBase.Nametype thing, whereNameis only defined on the base, and is not overridden (regardless of whether it is virtual or not). It's very useful to find all references to such members only for a specific class (with/without its derived classes).
Well, it is somewhat reasonable. I will reconsider it.
Yes, I very much like your elegant solution of not making the title too long with many generic parameters. I think the current solution works well. I was rather concerned a bit about discoverability of the fact that clicking on the title is not the same as clicking on the graphic symbol. But I am not sure what the right solution would be. I will give the new beta a try and let you know.
Well, it is somewhat reasonable. I will reconsider it.
Thank you. I hope you implement it, as otherwise there are sometimes 100s of search results to go through, when in fact you only need a few of them for a particular class.