Introduction
JetBrains ReSharper is an extension for Microsoft Visual Studio that provides numerous features to enhance developer productivity, surpassing that of developers who do not use ReSharper.
The purpose here is to introduce developers to several useful features that many developers just starting out with ReSharper may not be aware of and can immediately benefit from.
The author feels that ReSharper is a must-have addition to all developers' toolbox/
Also, check out 9 ReSharper Features Every .NET Developer Should Know.
Keyboard shortcuts
Once a developer learns which are the most important features for them, yet may not be used often.
Create a GitHub Markdown file, as shown below (see the sample in the source repository), where the last column contains a link to the command documentation.
Present in Visual Studio
Under Tools menu, External Tools create a new command (requires VS Code to be installed).
- Title: Resharper keyboard shortcuts
- Command: c:\users\USERNAME\AppData\Local\Programs\Microsoft VS Code\Code.exe
- Arguments: The name of the file, for instance resharperShortcuts.md
- Initial directory: Location of the file under arguments
Writing documentation
With a paid subscription to AI Assistant, a developer never need to worry about having excellent documentation. Yes, Visual Studio can generate help but no where as done with AI Assistant.
Extract Interface
This refactoring helps create a new interface based on a selected type. ReSharper suggests choosing members to be transferred to the new interface. After extraction, the original type is updated to implement the new interface.
As shown in the video, each interface is created in its own file, which is in the Models folder. The next step is to create a folder named Interfaces and drag the interfaces into the Interface folder, which will trigger Visual Studio to ask to update each Interfaces namespace.
Adjust Namespaces
This command is a bulk fix that helps you synchronize namespaces with folder structure in any scope, which can be as large as your whole solution.
This is a feature I used often. An example is selecting several classes from an instance of Visual Studio and dragging them to another instance of Visual Studio.
Once the above operation is complete, use 'adjust namespaces' to ensure the files match the current project namespaces.
In this example, the files are in a folder called Models.
- Right-click on the folder.
- Select Refactor
- Select Adjust Namespaces.
The following dialog appears; click the Next > button for ReSharper to adjust the namespace of each selected file.
Convert Static to Extension Method refactoring
This refactoring helps you convert a static method to an extension method, provided that the static method:
- Has at least one parameter.
- Resides in a non-generic, non-nested static class
This conversion is a time saver, as shown below, here, the shortcut is CTRL + Q, Q
Code templates
Code templates that help write common code constructs faster, surround existing code. Below is one of several template types.
Postfix templates
Postfix templates help you transform expressions that you have already typed without jumping backwards.
Example
A developer wants to use a foreach loop on a list with a variable named 'people'.
- Type people.f
- Select
foreach
A foreach is generated with the cursor on a suggested variable name, which the developer can use or change.
PostFix templates
Postfix templates help you transform expressions that you have already typed without jumping backwards — just type a dot after an expression and pick a template from the completion list.
For examples and source code see Learn Resharper PostFix and Source Templates
Regular expression to GeneratedRegex attribute
ReSharper automatically detects opportunities to refactor traditional regex usage into the modern [GeneratedRegex] attribute pattern.
Benefits of GeneratedRegex
- Performance: The regex is compiled and optimized at compile time, leading to faster startup times and lower runtime overhead compared to JIT-compiling the regex at runtime.
- Efficiency: It avoids the need for manual caching of Regex objects, as the generated code provides a singleton instance.
Example
Given the following code, using a regular expression.
public class Validator
{
public bool IsEmailValid(string email)
=> Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}
Place the text cursor in the regular expressions and a light bulb appears with the option Convert to GeneratedRegexAttruibte as shown below.
Accept the convert. During the conversion:
- The class becomes a partial class
- A second dialog is presented with recommendations for the name of the GeneratedRegex.
After the conversion.
public partial class Validator // Note: The class must be partial
{
public bool IsEmailValid(string email)
=> EmailRegex().IsMatch(email);
// ReSharper generates this attribute and partial method definition
[GeneratedRegex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$")]
private static partial Regex EmailRegex();
}
💡 See the above and a more complex code sample in the project ToGeneratedRegexSample.
INotifyPropertyChanged support
When a developer wants to use INotifyPropertyChanged, add : INotifyPropertyChanged after the class name, place the text cursor on INotifyPropertyChanged followed by selecting the first option under the red bulb.
Afterwards.
Next, place the text cursor on a property, select the yellow 💡 which ReSharper provides several options to implement property change.
Click a top-level menu item to implement for the current property while clicking a sub menu option while implement property change for all class properties.
In this case the selected option was To Property with 'SetField'.
Using AI Assistant
With the class open, try the following prompt:
Add INotifyPropertyChanged to Person class using SetField
Once satisfied, use the copy button in the top right corner for the response, followed by overwriting the current class.
Move Types into Matching Files
This feature is useful, for instance, when a developer creates several classes in a single file with the intention of later extracting each class to its own file.
The reason for creating multiple classes in one file is to avoid having to switch between each class in Visual Studio. The author does this because she can also create the classes faster than manually creating each class file. When finished, ReSharper will move each class to a new file.
Example
Given the following with several classes in a single file, Person.cs.
public class Person
{
public int Id { get; set; }
public bool Active { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
public required Gender Gender { get; set; }
public required Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public required string Street { get; set; }
public required string City { get; set; }
public required string State { get; set; }
}
public enum Gender
{
Male,
Female,
Other
}
- Right-click on the file in Solution Explorer
- Select Refactor
- Select
Move Type to Matching Files...
A dialog is presented with options. Click Next button to complete the move each class to new files.
💡 Try it in project MoveTypesIntoMatchingFiles\Models\Person.cs
Convert Anonymous to Named Type refactoring
Anonymous types are very convenient when you need to process a result of a query locally. However, if you need to pass the result around your program or if there are several queries that return the similar name/value parts, you may probably need a named type.
Example
In the following method, the variable duplicates can only be used within the method.
public static void GetDuplicateActiveMembers(IEnumerable<Member> list)
{
var duplicates = list
.GroupBy(member => new { member.FirstName, member.SurName, member.Active })
.Where(group => group.Key.Active && group.Count() > 1)
.Select(group => new
{
FullName = $"{group.Key.FirstName} {group.Key.SurName}",
Members = group.ToList()
})
.ToList();
}
To allow duplicates to be returned, place the cursor on new and press the shortcut key assigned to ReSharper_Anonymous2Declared command.
The following dialog appears; select a name, scope, and other options as needed. Click the Next button and ReSharper generates code.
Modify the return type as shown below.
public static List<GroupMember> GetDuplicateActiveMembers(IEnumerable<Member> list) =>
list
.GroupBy(member => (Name: member.FirstName, member.SurName, member.Active))
.Where(group =>
{
if (!group.Key.Active) return false;
return group.Count() > 1;
})
.Select(item =>
new GroupMember($"{item.Key.Name} {item.Key.SurName}", [.. item]))
.ToList();
The convert to name type can also be accessed from ReSharper menu.
Import missing namespaces
When you use types whose namespaces have not been imported in the file, ReSharper helps you locate these types and add the missing namespace import directives.
Example 1
A developer obtains the following code. Without ReSharper, Visual Studio will ask for missing using statements and provide recommendations.
Note
The reason for using a large example is for showing how well auto Import missing namespaces works.
public class Info
{
public readonly record struct CallerDetails(string? AssemblyName, string? AssemblyVersion,
string? TargetFramework, string? TypeName, string? MethodName, string? FilePath, int LineNumber);
[MethodImpl(MethodImplOptions.NoInlining)]
private static CallerDetails BuildCallerDetails(string? memberName, string? filePath, int lineNumber)
{
var callingAsm = Assembly.GetCallingAssembly();
string? typeName = null;
try
{
var st = new StackTrace(skipFrames: 1, fNeedFileInfo: false);
var frame = st.GetFrame(0);
typeName = frame?.GetMethod()?.DeclaringType?.FullName;
}
catch
{
// Best-effort; leave typeName null if anything goes sideways.
}
var asmName = callingAsm.GetName();
var framework = callingAsm.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
return new CallerDetails(
AssemblyName: asmName?.Name,
AssemblyVersion: asmName?.Version?.ToString(),
TargetFramework: framework,
TypeName: typeName,
MethodName: memberName,
FilePath: filePath,
LineNumber: lineNumber);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetCopyright()
{
var asm = Assembly.GetCallingAssembly();
var attr = asm.GetCustomAttribute<AssemblyCopyrightAttribute>();
return attr?.Copyright ?? "No copyright information found.";
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetCopyright(out CallerDetails caller, [CallerMemberName] string? memberName = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
{
caller = BuildCallerDetails(memberName, filePath, lineNumber);
return GetCopyright();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetCompany()
{
var asm = Assembly.GetCallingAssembly();
var attr = asm.GetCustomAttribute<AssemblyCompanyAttribute>();
return attr?.Company ?? "No company information found.";
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetCompany(out CallerDetails caller, [CallerMemberName] string? memberName = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
{
caller = BuildCallerDetails(memberName, filePath, lineNumber);
return GetCompany();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetProduct()
{
var asm = Assembly.GetCallingAssembly();
var attr = asm.GetCustomAttribute<AssemblyProductAttribute>();
return attr?.Product ?? "No product information found.";
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetProduct(out CallerDetails caller, [CallerMemberName] string? memberName = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
{
caller = BuildCallerDetails(memberName, filePath, lineNumber);
return GetProduct();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static Version GetVersion()
{
var asm = Assembly.GetCallingAssembly();
return asm.GetName().Version ?? new Version(1, 0, 0, 0);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static Version GetVersion(out CallerDetails caller, [CallerMemberName] string? memberName = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
{
caller = BuildCallerDetails(memberName, filePath, lineNumber);
return GetVersion();
}
}
With ReSharper, after pasting the code into, in this case, Info.cs, the following statements are added, which allow the code to compile.
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
Example 2
In some cases, auto import is not available, and the developer will be prompted as shown below.
Summary
ReSharper has more features than the average developer will ever need, while this is true. The best way to determine what is best for you is to take the time to read the great documentation and create a markdown file as described at the start of this article.
There are three subscriptions
- ReSharper which will fit every developer's needs
- dotUltimate which besides ReSharper has several helpful tools and JetBrains AI Pro
- All Products Pack which besides ReSharper has 18 tools.
How to decide? When unsure, select ReSharper or start a 30-day trial. Download a product and follow the instructions provided.
Author comment
The author has been using ReSharper since before 2015.
There are times when she has three instances of Microsoft Visual Studio open at once and ReSharper never hinders performance.
















Top comments (0)