forked from icsharpcode/NRefactory
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME
229 lines (173 loc) · 11.1 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
Overview of the NRefactory library:
ICSharpCode.Editor:
Interfaces that abstract from the text editor control used.
Maybe future AvalonEdit versions will directly implement these interfaces, but you could also write adapters for other editors.
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.Ast:
Abstract Syntax Tree for C#
ICSharpCode.NRefactory.CSharp.Resolver:
Semantic analysis for C# (resolving the meaning of expressions)
ICSharpCode.NRefactory.CSharp.Analysis:
Semantic analysis for C# (additional analysis functionality)
ICSharpCode.NRefactory.Documentation:
Classes for working with .xml documentation files.
ICSharpCode.NRefactory.PatternMatching:
Provides classes for pattern matching over the C# and VB ASTs.
ICSharpCode.NRefactory.Util:
Various helper classes.
ICSharpCode.NRefactory.VB.Ast: (in the separate ICSharpCode.NRefactory.VB assembly)
Abstract Syntax Tree for VB
Dependencies:
.NET 3.5 or .NET 4.0
Mono.Cecil 0.9.4
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.
The C# AST makes use of special null nodes when accessing the getter of an AST property and no
child node with that role exists. Check the IsNull property to test whether a node is a null node.
Null nodes are not considered to be part of the AST (e.g. they don't have a parent).
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 with a value-type constraint.
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.
Q: What's the difference between the .NET 3.5 and .NET 4.0 versions?
A: As for visible API difference, not much. The .NET 4.0 build has some additional overloads for a few methods,
taking a System.Threading.CancellationToken to allow aborting a resolve run.
Internally, the .NET 4.0 version might be tiny bit more performant because it uses covariance for IEnumerable,
where the .NET 3.5 version has to allocate wrapper objects instead.
Both versions support loading assemblies of all .NET versions (1.0 to 4.0); and both support C# 4.0.