1 changed files with 191 additions and 154 deletions
@ -1,154 +1,191 @@
@@ -1,154 +1,191 @@
|
||||
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. |
||||
[Actually, sometimes static state is used for caches, but those uses are thread-safe.] |
||||
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, internal caches in the library are not used when passing a mutable ITypeResolveContext. |
||||
|
||||
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. |
||||
It is guaranteed not to change (within the using block), so the library may cache some information. (TODO: give example of a cache) |
||||
Once the return value is disposed, the read-locks are released (and the caches are cleared). |
||||
|
||||
|
||||
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. |
||||
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 result, 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.) |
||||
|
||||
A: If you've previously used the .NET Reflection API, the concept of type references will be new |
||||
to you. |
||||
|
||||
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)) |
||||
|
||||
|
||||
Q: What's in an ITypeResolveContext? |
||||
|
||||
A: An ITypeResolveContext is an environment for looking up namespaces and types. |
||||
Usually, a resolve context will represent a set of projects. |
||||
Most of the time, that set will be the current project, plus the direct references of the |
||||
current project. |
||||
|
||||
Every project content on its own is a type resolve context (IProjectContent extends |
||||
ITypeResolveContext); but (with the exception of mscorlib) isn't useful for resolving types as |
||||
you also need the references. |
||||
To represent a set of projects, the class CompositeTypeResolveContext can be used. |
||||
|
||||
|
||||
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. |
||||
[Actually, sometimes static state is used for caches, but those uses are thread-safe.] |
||||
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, internal caches in the library are not used when passing a mutable |
||||
ITypeResolveContext. |
||||
|
||||
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. |
||||
It is guaranteed not to change (within the using block), so the library may cache some |
||||
information. (TODO: give example of a cache) |
||||
Once the return value is disposed, the read-locks are released (and the caches are cleared). |
||||
|
||||
|
||||
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. |
||||
|
||||
Loading…
Reference in new issue