Skip to content

madkat/NRefactory

 
 

Repository files navigation

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.Ast:
	Abstract Syntax Tree for C#

ICSharpCode.NRefactory.CSharp.Resolver:
	Semantic analysis for C#
	
ICSharpCode.NRefactory.Util:
	Various helper classes.

ICSharpCode.NRefactory.VB.Dom: (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.

About

Repository for future NRefactory version

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C# 100.0%