Working with Types in Your Analyzer
If you want to write an analyzer that checks for certain conditions for a given type, you are going to have to familiarize yourself in working with types in Roslyn. Types returned to you from various Roslyn methods will return to you an object that implements ITypeSymbol
. This interface has various methods that can provide you with the information you need to make decisions in your analyzer. You can get types for various nodes in the semantic model by using the SemanticModel.GetTypeInfo
and then using either the Type
or ConvertedType
off of the TypeInfo
that is returned. The difference between the Type
and ConvertedType
is that the ConvertedType
is the type after any implicit conversions have been applied. If there are no implicit conversions, then Type
and ConvertedType
are identical.
There are some types that are special and can be checked by using the SpecialKind
enum. For example, if you have an ITypeSymbol
and you want to check if it is a string, you could simply do the following.
if (variableTypeInfo.SpecialType == SpecialType.System_String)
{
}
One question that you may have is how to get an ITypeSymbol
if it is not defined in the SpecialKind
enum. I had the same question and posted it to StackOverflow. I got an answer from Kevin Pilch-Bisson (@Pilchie) which states:
The normal pattern for doing this is to use Compilation.GetTypeByMetadataName(), and then compare that ITypeSymbol with The one you got back from SemanticModel.GetTypeInfo().
Note: Make sure to use .Equals to compare ITypeSymbol instances, as some of them do not guarantee reference identity.
So, if we register a CompilationStartAction
, then we can get at a type by simply using the GetTypeByMetadataName
method:
var arrayListType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.ArrayList");
So, now that I have the ArrayList
ITypeSymbol
, I can now use that to compare to other ITypeSymbol
interfaces I get back from the semantic model.
For example, I could have an analyzer that prohibits the usage of ArrayList
. I can register a SyntaxNodeAction
for all ObjectCreationExpressoin
syntax kinds and use the GetTypeInfo
method to get the type of the creation and determine if it is an array list.
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(AnalyzeArrayList);
}
private static void AnalyzeArrayList(CompilationStartAnalysisContext compilationContext)
{
var arrayListType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.ArrayList");
compilationContext.RegisterSyntaxNodeAction(syntaxContext =>
{
var variableTypeInfo = syntaxContext.SemanticModel.GetTypeInfo(syntaxContext.Node).Type as INamedTypeSymbol;
if (variableTypeInfo == null)
return;
if (variableTypeInfo.Equals(arrayListType))
{
syntaxContext.ReportDiagnostic(Diagnostic.Create(Rule, syntaxContext.Node.GetLocation()));
}
}, SyntaxKind.ObjectCreationExpression);
}
As the name implies, the GetTypeByMetadataName
uses the metadata name of the type to find the type. One place where this might catch you is when dealing with generics. For example, when working with a dictionary, you might be tempted to try one of the following:
var dictionaryType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary <string, string>");
var dictionaryType2 = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary <TKey, TValue>");
But the Metadata name for the generic dictionary is Dictionary`2
. For generics it is always the name of the type followed by the arity (number of type parameters) that make up the metadata name (e.g. List`1
, Tuple`5
). For more info, you can read up on type names and arity encoding.
So the correct way to get a generic dictionary is
var dictionaryType2 = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");
If you are still unsure of the correct value to use for the GetTypeByMetadataName
method, you can look at the MetadataName
property that is available on all objects that implement ISymbol
.
As you can see working with types in code analyzers is not too difficult once you know where to look and how to obtain the types you care about.