Any kind of manipulation of object-oriented structures necessarily involve working with types such as
string or, say,
MyType<U,V>. Predictably, ReSharper has a number of data structures that allow plugin writers to identify and introspect types, whether these types are available as code or not.
At the very top of the ‘hierarchy of types’, we have the
IExpressionType interface. This is a wide-reaching interface that covers any type that an expression may be and also happens to cover the principal ‘concrete’ interface
IType, the interfaces and classes directly implementing
IExpressionType are typically language-specific. Examples of such expression types include C#‘s
EventType or VB’s
IExpressionType defines a fundamental contact which all expression types adhere to. Thus, it makes sense to describe some of its members.
IsResolved- this property tells us whether this type has actually been resolved to something. For example, if the expression type is
StringBuilder, ReSharper will resolve it to
System.Text.StringBuilder. However, if the expression type is
Fooand this class does not exist yet,
IsResolvedwill be false. This also covers the thorny issue of type substitution, which we’ll discuss later.
IsUnknown- determines whether this type is unknown, i.e. it cannot be properly presented.
IsValid()- checks if the type refers to valid declared elements.
IsExplicitlyConvertibleTo()check whether this type is implicitly or explicitly convertible to some type (
IType) via a type conversion rule. Type conversion rules are by themselves singleton classes that implement the
ITypeConversionRuleinterface. For example, to use C# type conversion rule, one would use
So where is
IExpressionType used? Well, the most obvious example is if you’ve got an expression (i.e., an
IExpression), you can use its
GetExpressionType() method to get the expression’s
IExpressionType. Of course, in most cases the expression type is expected to be an actual object type in which case it makes sense to call
IExpressionType.ToIType() method. Of course, it returns
null in case when the expression in question is not an
Let us now take a look at the
IType interface is used to represent various type constructs - not just individual types, but constructs such as anonymous types, arrays, pointers, and others.
IType is also an interface, which has some members that we need to discuss:
GetPresentableName- this returns the presentable name of the type for a particular language, such as C# or VB.NET. The string returned is ’presentable’ in the sense that the shortest and most concise string is used. For example, a 32-bit integer will be presented in C# as
GetScalarType()returns a declared type that corresponds to the ’scalar’ portion of the type. So, if your
ITypehappens to be
int *(a pointer to an
int), the returned
IDeclaredTypewill correspond to
Classifyis a property that helps you determine if the type is a value or reference type.
nullis returned in case this cannot be determined.
Of all the interfaces that implement
IType, the one we’re most interested in is
IDeclaredType, so let’s discuss what it’s all about.
IDeclaredType is the interface type you’ll be seeing most when working with types. Essentially, an
IDeclaredType is an interface for a type that might have a declaration (i.e., it might correspond to some
ITypeElement). Examples of declared types might be
Foo, provided you have a declaration of
Foo, of course.
The interface’s most important member is
GetTypeElement(), which may return a corresponding
null if one isn’t available. Other members include:
GetClrName()- return a CLR name (
IClrTypeName) of the type. This interface can then be used to get the short and full names of the type, to acquire lists of namespace names for this type, and so on.
GetSubstitution()- returns the substition that is made for this type. Substitutions are mechanisms for replacing type parameters (i.e., generic paramers) with concrete types.
Resolve()attempts to resolve this type. The resulting
IsValid()method can be called to check if resolution succeeded.
IsSubtypeOf()- determines whether this type is a subtype of some other
But that’s not all! In addition to the above members, there is also a
DeclaredTypeExtensions class, which provides additional utility methods. For example, the
GetSuperTypes() method returns an enumeration of
IDeclaredTypes corresponding to this type’s parents.
We’ve looked at a hierarchy of types, but how does this help us? Well, now that we know a little about types, we can start looking at yet another hierarchy -- a hierarchy of declared elements.
IDeclaredElement is ReSharper’s über-interface for defining various physical language constructs in ReSharper. By physical we assume that a PSI tree has been built for them, in other words, we have the source code that defines this element. Declared elements are used to represent not just different constructs (such as a CLR class or a CSS function) but also different facets of structures.
Let us attempt a top-down overview of the
IDeclaredElement interface and its implementers by looking first at its members.
GetDeclarations()returns the declarations (i.e.,
IDeclarations) that this declarated element contains. We’ll get to declarations in a while.
ShortNamereturns a ‘friendly name’ of a declared element.
GetElementTypereturns the type of the declared element. This value is typically language-related, i.e., for a C# element we’d get, for example, a
PresentationLanguageaffects the language that is used to create this type.
GetSourceFiles()returns a collection of
IPsiSourceFile` s that contain this type’s definition. (Remember that thanks to
partialtypes, a type definition may span multiple files.)
HasDeclarationsIn()lets you check whether or not any of the declared element’s declarations appear in a particular file.
Let’s drill down one level in the inheritance hierarchy and discuss the various declared element types.
Declared Element Types
Most of the types under
ILabel that appear at this stage simply because there is no other suitable location for them.
In the context of this guide, we’re going to take a look at the
IClrDeclaredElement interface which, predictably, reflects the declared elements for CLR (C#, VB.NET) languages.
This interface doesn’t contain much, its main responsibility being to delineate the fact that its implementers are CLR declared elements. Apart from yielding an
IPsiModule handle, it can also return its containing type (if any) via
GetContainingType() or containing type member via
This interface is interesting to us because of its immediate descendants. The ones worth mentioning are:
IAttributesOwner- this interface is implemented by any declared element that can be decorated with attributes. You would typically use this interface to manipulate the attributes that some element has attached to it.
IParametersOwner- if the declaring element takes parameters (note, we’re talking about ordinary parameters, not type parameters), this interface helps us manipulate them.
ITypeOwner- means the declared element has a type of its own.
ITypeParametersOwner- means this declared element has type parameters. Examples would be classes and methods.
While on the subject of types as such, let’s discuss the CLR types and how they are exposed. Walking down the inheritance hierarchy, underneath
IClrDeclaredElement we first encounter the
ITypeParametersOwner interface which, as mentioned previously, is implemented by any construct that can have type parameters. This interface exposes just one property, a list of
ITypeParameter objects that correspond to type parameters.
What’s more interesting, however, is the interface inheriting from it. This interface is called
ITypeElement. What’s important about this interface is that it represents a CLR type, such as a class, structure, enumeration or delegate. This interface is inherited by interfaces such as
IStruct, and so on. Let’s discuss some of its members:
NestedTypes- this property is itself a list of
ITypeElemententities representing the nested types.
Methods, and so on --- all these properties yield enumerations of appropriate type members.
GetMembers()- helps you get all of the above in a single list of
GetContainingNamespace()- does exactly what it says, and may return
nullif the type isn’t within a namespace (i.e., it is at global scope).
There is also a
TypeElementExtensions class which provides additional functionality. For example, to check if a type is a descendant of another type (i.e., has another type above it somewhere in the inheritance chain), you can use the