Show / Hide Table of Contents

Fast Reflection

Invocation of reflected members in .NET is slow. This happens because late-binding invocation should provide type check of arguments for each call. DotNext Reflection library provides a way to invoke reflected members in strongly typed manner. It means that invocation parameters are typed and type safety is guaranteed by compiler. Moreover, this feature allows to invoke members with the same performance as they called without reflection. The reflected member can be converted into appropriate delegate instance for caching and further invocation. The binding process still performed dynamically and based on .NET reflection.

Reflector class provides methods for reflecting class members. The type of delegate instance which represents reflected member describes the signature of the method or constructor. But what the developer should do if one of constructor or method parameteters has a type that is not visible from the calling code (e.g. type has internal visibility modifier and located in third-party library)? This issue is covered by Reflection library with help of the following special delegate types:

  • Function<A, R> for static methods with return type
  • Function<T, A, R> for instance methods with return type
  • Procedure<A> for static methods without return type
  • Procedure<T, A> for instance methods without return type

These delegates can describe signature of arbitrary methods or constructors with a little performance cost: all arguments will passed through stack. As a result, they can be used if developer don't want to introduce a new delegate type for some untypical signatures (with ref or out parameters).

Combination of various delegate signatures and Reflector class provide configurable approach to fast reflection and allows to choose between convenience and performance. Moreover, it requires compile-time some knowledge about underlying types of parameters and declaring type. To reduce this complexity, .NEXT Reflection library offers lightweight fast reflection API represented by non-generic overloaded version of Unreflect extension method. Lightweight implementation is a dynamic compilation of member access code and exposes unified API surface for all supported member types in the form of single DynamicInvoker delegate type. Invocation API is very similar to reflection provided by .NET out-of-the-box, but much more faster.

Constructor

Constructor can be reflected as delegate instance.

using System.IO;
using DotNext.Reflection;

Func<byte[], bool, MemoryStream> ctor = typeof(MemoryStream).GetConstructor(new[] { typeof(byte[]), typeof(bool) }).Unreflect<Func<byte[], bool, MemoryStream>>();
using(var stream = ctor(new byte[] { 1, 10, 5 }, false))
{

}

The same behavior can be achieved using Function special delegate:

using System.IO;
using DotNext.Reflection;

Function<(byte[] buffer, bool writable), MemoryStream> ctor = typeof(MemoryStream).GetConstructor(new[] { typeof(byte[]), typeof(bool) }).Unreflect<Function<(byte[], bool), MemoryStream>>();

var args = ctor.ArgList();
args.buffer = new byte[] { 1, 10, 5 };
args.writable = false;
using var stream = ctor(args);

Moreover, it is possible to use custom delegate type for reflection:

using DotNext.Reflection;
using System.IO;

internal delegate MemoryStream MemoryStreamConstructor(byte[] buffer, bool writable);

MemoryStreamConstructor ctor = typeof(MemoryStream).GetConstructor(new[] { typeof(byte[]), typeof(bool) }).Unreflect<MemoryStreamConstructor>();
using var stream = ctor(new byte[] { 1, 10, 5 }, false);

Lightweight object construction can be achieved using overloaded non-generic Unreflect method:

using DotNext.Reflection;
using System.IO;

var ctor = typeof(MemoryStream).GetConstructor(new[] { typeof(byte[]), typeof(bool) }).Unreflect();
using var stream = (MemoryStream)ctor(null, new byte[] { 1, 10, 5}, false);

Method

Static or instance method can be reflected as delegate instance. In case of instance method, first argument of the delegate should accept this argument:

  • T for reference type
  • ref T for value type
using System.Numerics;
using DotNext.Reflection;

internal delegate byte[] ToByteArray(ref BigInteger @this);

var toByteArray = typeof(BigInteger).GetMethod(nameof(BigInteger)).Unreflect<ToByteArray>();
BigInteger i = 10;
var array = toByteArray(ref i);

If method contains ref our our parameter then then it is possible to use custom delegate, Function or Procedure. The following example demonstrates how to use Function to call a method with out parameter.

using DotNext.Reflection;

Function<(string text, decimal result), bool> tryParse = typeof(decimal).GetMethod(nameof(decimal.TryParse), new[]{typeof(string), typeof(decimal).MakeByRefType()}).Unreflect<Function<(string, decimal), bool>>();

(string text, decimal result) args = tryParse.ArgList();
args.text = "42";
tryParse(args);
decimal v = args.result;    //v == 42M

args value passed into Function instance by reference and contains all necessary arguments in the form of value tuple.

Let's assume than type of text parameter is not known at compile time or unreachable from source code because the type is declared in external library and has internal visibility modifier. In this case, the type of such parameter can be replaced with object data type. Of course, it will affect performance but still be much faster than classic .NET reflection.

using DotNext.Reflection;

Function<(object text, decimal result), bool> tryParse = typeof(decimal).GetMethod(nameof(decimal.TryParse), new[]{typeof(string), typeof(decimal).MakeByRefType()}).Unreflect<Function<(object, decimal), bool>>();

(object text, decimal result) args = tryParse.ArgList();
args.text = "42";
tryParse(args);
decimal v = args.result;    //v == 42M

Lightweight method invocation can be achieved using overloaded non-generic Unreflect method:

using DotNext.Reflection;

var tryParse = typeof(decimal).GetMethod(nameof(decimal.TryParse), new[]{typeof(string), typeof(decimal).MakeByRefType()}).Unreflect();
object[] args = {"42", decimal.Zero};
tryParse(null, args);
decimal v = (decimal)args[1];

Field

Static or instance field can obtained from FieldInfo using Unreflect extension method declared in Reflector class. This feature gives the power to work with field values using Reflection without performance loss.

Important

Managed pointer to the field value is mutable even if field is readonly. As a result, you can modify value of such field. It is responsibility of the developer to control access to read-only fields.

This is not the only way to obtain direct access to the field. Field<V> and Field<T,V> that can be returned by Type<T>.Field<T> provide access to static and field value respectively.

The following example demonstrates how to obtain managed pointer to the static and instance fields:

using DotNext.Reflection;
using System.Reflection;

class MyClass
{
	private static long StaticField;

	private string instanceField;

	public MyClass(string str) => instanceField = str;
}

//change value of static field
ref long staticField = ref typeof(MyClass).GetField("StaticField", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Unreflect<long>().Value;
staticField = 42L;

//change value of instance field
var obj = new MyClass();
ref string instanceField = ref obj.GetClass().GetField("instanceField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly).Unreflect<string>()[obj];
instanceField = "Hello, world";

Lightweight field access can be achieved using overloaded non-generic Unreflect method which supports various optimization:

  • Obtain field getter only
  • Obtain field setter only
  • Obtain field setter and getter combined into single instance of DynamicInvoker

Third option is slower in comparison with others. Therefore if you expect one-directional access to the field then use proper optimization.

The following example demonstrates all possible optimization modes when generating field accessor:

using DotNext.Reflection;
using System;
using System.Reflection;

var obj = new MyClass("Hello, world!");
//generate read-only accessor
var field = obj.GetClass().GetField("instanceField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
DynamicInvoker invoker = field.Unreflect(BindingFlags.GetField);	
Console.WriteLine(invoker(obj));	//prints Hello, world!

//generate write-only accessor
invoker = field.Unreflect(BindingFlags.SetField);
invoker(obj, "New field value");	//obj.instanceField = "New field value"

//generate read-write accessor
invoker = field.Unreflect();
invoker(obj, "Hello, world!");
Console.WriteLine(invoker(obj));	//prints Hello, world!

Performance

Invocation of members through special delegates is not a free lunch: you pay for passing arguments through the stack. But it still much faster than classic .NET Reflection. The following list describes performance impact using different approaches to reflection (from fast to slow).

Reflective call Performance
Custom delegate type or predefined delegate type which exactly describes the signature of expected method the same or comparable to direct call (with nanoseconds overhead)
Special delegate types x1,4 slower than direct call
Special delegate types with one or more unknown parameter types (when object used instead of actual type) x2/x3 slower than direct call
Dynamically compiled DynamicInvoker x1.5/x2 slower than direct call and causes heap allocation of arguments of value type
Classic .NET Reflection x10/x50 slower than direct call

Read more about performance in Benchmarks article.

  • Improve this Doc
☀
☾
Back to top Generated by DocFX