#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

8.5 KiB

Overview of the NRefactory library:

ICSharpCode.NRefactory.TypeSystem:
Contains a language-independent representation of the .NET type system.

ICSharpCode.NRefactory.TypeSystem.Implementation:
Contains base classes that help implementing the type system interfaces.

ICSharpCode.NRefactory.CSharp.Dom:
Abstract Syntax Tree for C#

ICSharpCode.NRefactory.CSharp.Resolver:
Semantic analysis for C#

Null-Object pattern:
The NRefactory library makes extensive use of the null object pattern.
As a reult, NullReferenceExceptions should be very rare when working with this library.
In the type system, both ITypeReference and IType use SharedTypes.UnknownType to represent unknown types.
Unless the method is documented otherwise, no method or property returning a ITypeReference or IType will return null.
When adding to this library, to try to keep such uses of null rare.

Note that the null object pattern is not used for ITypeDefinition:
IProjectContent.GetClass() returns null when a type is not found. Take care to abort your operation or substitute UnknownType
instead of passing the null to code expecting an IType.

The pattern also extends to the C# resolver, which always produces a ResolveResult, even in error cases.
Use ResolveResult.IsError to detect resolver errors. Also note that many resolver errors still have a meaningful type attached,
this allows code completion to work in the presence of minor semantic errors.

FAQ:
Q: What is the difference between types and type definitions?

A: Basically, a type (IType) is any type in the .NET type system:
- an array (ArrayType)
- a pointer (PointerType)
- a managed reference (ByReferenceType)
- a parameterized type (ParameterizedType, e.g. List<int>)
- a type parameter (ITypeParameter, e.g. T)
- or a type definition (ITypeDefiniton)

Type definitions are only classes, structs, enums and delegates.
Every type definition is a type, but not every type is a type definition.
NRefactory's ITypeDefinition derives from IType, so you can directly use any type definition as a type.
In the other direction, you could try to cast a type to ITypeDefinition, or you can call the GetDefinition()
method. The GetDefinition() method will also return the underlying ITypeDefinition if given a parameterized type,
so "List<int>".GetDefinition() is "List<T>".


Q: What is the difference between types and type references?
I've seen lots of duplicated classes (ArrayType vs. ArrayTypeReference, etc.)

NRefactory has the concept of the "project content": every assembly/project is stored independently from other assemblies/projects.
It is possible to load some source code into a project which contains the type reference "int[]" without having to load
mscorlib into NRefactory.
So inside the entities stored for the project, the array type is only referenced using an ITypeReference.
This interface has a single method:
interface ITypeReference {
IType Resolve(ITypeResolutionContext context);
}
By calling the Resolve()-method, you will get back the actual ArrayType.
At this point, you have to provide the type resolution context:

Note that every type can also be used as type reference - the IType interface derives from ITypeReference.
Every IType simply returns itself when the Resolve()-method is called.
Types are often directly used as references when source and target of the reference are within the same assembly.

Because an ArrayType must have an IType as element type, we also need the ArrayTypeReference to represent an array
of a type that's not yet resolved. When resolved, the ArrayTypeReference produces an array type:
new ArrayTypeReference(r).Resolve(context) = new ArrayType(r.Resolve(context))

A: If you've previously used the .NET Reflection API, the concept of type references is new to you.


Q: How do I get the IType or ITypeReference for a primitive type such as string or int?

A: Please use:
TypeCode.Int32.ToTypeReference().Resolve(context)
Skip the Resolve() call if you only need the type reference.

ReflectionHelper.ToTypeReference is very fast if given a TypeCode (it simply looks up an existing type reference in an array),
and your code will benefit from caching of the resolve result (once that gets implemented for these primitive type references).

Avoid using "context.GetClass(typeof(int))" - this call involves Reflection on the System.Type being passed,
cannot benefit from any caching implemented in the future, and most importantly: it may return null.
And do you always test your code in a scenario where mscorlib isn't contained in the resolve context?
The approach suggested above will return SharedTypes.UnknownType when the type cannot be resolved, so you
don't run into the risk of getting NullReferenceExceptions.


Q: Is it thread-safe?

A: This question is a bit difficult to answer.
NRefactory was designed to be usable in a multi-threaded IDE.
But of course, this does not mean that everything is thread-safe.

First off, there's no hidden static state, so any two operations working on independent data can be executed concurrently.
TODO: what about the C# parser? gmcs is full of static state...

Some instance methods may use hidden instance state, so it is not safe to e.g use an instance of the CSharp.Resolver.Conversions class
concurrently. Instead, you need to create an instance on every thread.

In the case of project contents, it is desirable to be able to use them, and all the classes in that project content,
on multiple threads - for example to provide code completion in an IDE while a background thread parses more files and adds
them to the project content.

For this reason, the entity interfaces (ITypeDefinition, IMember, etc.) are designed to be freezable. Once the Freeze() method is
called, an entity instance becomes immutable and thread-safe.

Whether an ITypeResolveContext is thread-safe depends on the implementation:
TypeStorage: thread-safe for concurrent reads, but only if it's not written to (see XML documentation on TypeStorage)
CecilProjectContent: immutable and thread-safe
SimpleProjectContent: fully thread-safe
CompositeTypeResolveContext: depends on the child contexts

Usually, you'll work with a set of loaded projects (SimpleProjectContents)
and loaded external assemblies (CecilProjectContent).
A CompositeTypeResolveContext representing such a set is thread-safe.

Hoever, some algorithms can become confused if two GetClass() calls with same arguments produce different
results (e.g. because another thread updated a class definition).
Also, there's a performance problem: if you have a composite of 15 SimpleProjectContents and the resolve algorithm
requests 100 types, that's 1500 times entering and leaving the read-lock.
Moreoever, the ITypeResolveContext methods that return collections need to create a copy of the collection.

The solution is to make the read lock more coarse-grained:
using (var syncContext = compositeTypeResolveContext.Synchronize()) {
resolver.ResolveStuff(syncContext);
}
On the call to Synchronize(), all 15 SimpleProjectContents are locked for reading.
The return value "syncContext" can then be used to access the type resolve context without further synchronization overhead.
Once the return value is disposed, the read-locks are released.


Q: What format do the .ToString() methods use?

A: They don't use any particular format. They're merely intended as a debugging aid.
Currently .ToString() usually matches .ReflectionName, but that may change in the future.


Q: Why are there extension methods IType.IsEnum() and IType.IsDelegate(), but no IType.IsStruct() or IType.IsInterface()?

A: Because if you're asking whether a type is a struct, it's very likely that you're asking the wrong question.
The distinction between class/struct/interface/enum/delegate is important in the world of type definitions, and there's
ITypeDefinition.ClassType to address this. But the distinction isn't so important in the world of types.

If whatever you are doing works with struct-types, then it likely will also work with enum-types, and also
with type parameters constraint to be a value-type.
So instead of asking IsStruct(), you really should be asking: IType.IsReferenceType == false

Enums and delegates are special because you can do special things with those types (e.g. subtract them from each other).
If you really need to know, you can do "type.GetDefinition() != null && type.GetDefinition().ClassType == WhatIWant" yourself,
but for the most part you should be fine with IsReferenceType, IsEnum and IsDelegate.